SwiftUI:使用matchedGeometryEffect控制视图上的zIndex

Posted

技术标签:

【中文标题】SwiftUI:使用matchedGeometryEffect控制视图上的zIndex【英文标题】:SwiftUI: control zIndex on views with matchedGeometryEffect 【发布时间】:2021-03-10 11:02:51 【问题描述】:

我正在 SwiftUI 中构建一个自定义 SegmentedPicker,其中选择器会调整其大小以适应每个选择器项目的框架。我已经在这篇文章 (Inspecting the View Tree) 的启发下使用 PreferenceKeys 来处理统一大小的项目,如下所示:

我认为我可以通过使用.matchedGeometryEffect() 来大大简化我的实现并完全避免使用PreferencyKeys。我的想法是仅在选择该项目时在每个项目后面显示一个选择器,并使用 .matchedGeometryEffect() 同步过渡。除了选择器将位于先前选择的项目前面的问题之外,几乎一切都在工作。我尝试显式设置zIndex,但似乎不影响结果:

代码:

struct MatchedGeometryPicker: View 
    @Namespace private var animation
    @Binding var selection: Int
    let items: [String]
    
    var body: some View 
        HStack 
            ForEach(items.indices)  index in
                ZStack 
                    if isSelected(index) 
                        Color.gray.clipShape(Capsule())
                            .matchedGeometryEffect(id: "selector", in: animation)
                            .animation(.easeInOut)
                            .zIndex(0)
                    
                    
                    itemView(for: index)
                        .padding(7)
                        .zIndex(1)
                
                .fixedSize()
            
        
        .padding(7)
    
    
    func itemView(for index: Int) -> some View 
        Text(items[index])
            .frame(minWidth: 0, maxWidth: .infinity)
            .foregroundColor(isSelected(index) ? .black : .gray)
            .font(.caption)
            .onTapGesture  selection = index 
    
    
    func isSelected(_ index: Int) -> Bool  selection == index 

ContentView:

struct ContentView: View 
    @State private var selection = 0
    
    let pickerItems = [ "Item 1", "Long item 2", "Item 3", "Item 4", "Long item 5"]
    
    var body: some View 
        MatchedGeometryPicker(selection: $selection, items: pickerItems)
            .background(Color.gray.opacity(0.10).clipShape(Capsule()))
            .padding(.horizontal, 5)
        
    

任何想法如何解决这个问题?

【问题讨论】:

为什么使用 PreferenceKeys 的工作代码不适合使用和尝试这种方式? @swiftPunk with PreferenceKeys 如果每个项目都具有相同的框架,它会很好地工作,但我仍在尝试解决选择器尺寸的动画问题。使用.matchedGeometryEffect(),选择器可以完美地动画位置和尺寸变化,并且代码更简单。我只需要弄清楚如何解决分层问题... 【参考方案1】:

当项目具有不同的帧大小时,我设法解决了使用 PreferenceKeys 的选择器实现时遇到的所有动画问题。这并不能解决我在使用zIndex.matchedGeometryEffect() 时遇到的问题,因此我不会接受我自己的答案,但我会将其作为参考发布,以备将来有人需要时参考。

代码:

public struct PKPicker: View 
    @Binding var selection: Int
    @State private var frames: [CGRect] = []
    let items: [String]
    
    public init(
        selection: Binding<Int>,
        items: [String])
    
        self._selection = selection
        self._frames = State(wrappedValue: Array<CGRect>(repeating: CGRect(),
                                                         count: items.count))
        self.items = items
    
    
    public var body: some View 
        ZStack(alignment: .topLeading) 
            selector
            HStack 
                ForEach(items.indices)   index in
                    itemView(for: index)
                        
                
            
        
        .onPreferenceChange(PKPickerItemPreferenceKey.self)  preferences in
            preferences.forEach  frames[$0.id] = $0.frame 
        
        .coordinateSpace(name: "picker2")
        
    
    
    var selector: some View 
        Color.gray.opacity(0.25).clipShape(Capsule())
            .frame(width: frames[selection].size.width,
                   height: frames[selection].size.height)
            .offset(x: frames[selection].minX, y: frames[selection].minY)
    
    
    func itemView(for index: Int) -> some View 
        Text(items[index])
            .fixedSize()
            .padding(7)
            .foregroundColor(isSelected(index) ? .black : .gray)
            .font( .caption)
            .onTapGesture  selection = index 
            .background(PKPickerItemPreferenceSetter(id: index))
            
    
    
    func isSelected(_ index: Int) -> Bool 
        index == selection
    


struct PKPickerItemPreferenceData: Equatable 
    let id: Int
    let frame: CGRect


struct PKPickerItemPreferenceKey: PreferenceKey 
    typealias Value = [PKPickerItemPreferenceData]

    static var defaultValue: [PKPickerItemPreferenceData] = []
    
    static func reduce(
        value: inout [PKPickerItemPreferenceData],
        nextValue: () -> [PKPickerItemPreferenceData])
    
        value.append(contentsOf: nextValue())
    


struct PKPickerItemPreferenceSetter: View 
    let id: Int
    let coordinateSpace = CoordinateSpace.named("picker2")
    
    var body: some View 
        GeometryReader  geometry in
            Color.clear
                .preference(key: PKPickerItemPreferenceKey.self,
                            value: [PKPickerItemPreferenceData(
                                        id: id, frame: geometry.frame(in: coordinateSpace))])
        
    

ContentView

结构内容视图:查看 @State 私有变量选择 = 0

let pickerItems = [ "Item 1", "Long item 2", "Item 3", "Item 4", "Long Item 5"]

var body: some View 
    PKPicker(selection: $selection.animation(.easeInOut), items: pickerItems)
        .padding(7)
        .background(Color.gray.opacity(0.10).clipShape(Capsule()))
        .padding(5)
    

结果:

【讨论】:

以上是关于SwiftUI:使用matchedGeometryEffect控制视图上的zIndex的主要内容,如果未能解决你的问题,请参考以下文章

在SwiftUI项目中使用UIKit(SwiftUI和UIKit混合开发)

如何在 SwiftUI 中使用 Realm

使用 SwiftUI 实现应用内购买 [关闭]

SwiftUI 使用SwiftUI实现跑马灯效果 Marquee

SwiftUI 使用SwiftUI实现跑马灯效果 Marquee

SwiftUI 使用SwiftUI实现跑马灯效果 Marquee