Swiftui 快捷键/首选键:如何避免滚动时移动矩形?

Swiftui 快捷键/首选键:如何避免滚动时移动矩形?,swiftui,Swiftui,我正在使用一个可滚动的选项卡栏,它为所选的选项卡提供了一个移动的背景 解决方案基于PreferenceKeys;然而,我有一个问题,使移动背景相对于标签稳定。当前,它在滚动时移动,这是不需要的;相反,它应相对于选项卡项进行固定,并与选项卡项一起滚动 为什么会这样,如何避免?删除滚动视图时,背景将正确移动到所选选项卡项。TabItemButton只是一个带有特殊标签的按钮 struct TabBar: View { @EnvironmentObject var service:

我正在使用一个可滚动的选项卡栏,它为所选的选项卡提供了一个移动的背景

解决方案基于
PreferenceKey
s;然而,我有一个问题,使移动背景相对于标签稳定。当前,它在滚动时移动,这是不需要的;相反,它应相对于选项卡项进行固定,并与选项卡项一起滚动

为什么会这样,如何避免?删除
滚动视图时,背景将正确移动到所选选项卡项。
TabItemButton
只是一个带有特殊标签的
按钮

struct TabBar: View {


        @EnvironmentObject var service: IRScrollableTabView.Service


        // We support up to 15 items.
        @State private var rects: [CGRect] = Array<CGRect>(repeating: CGRect(), count: 15)


        var body: some View {

            GeometryReader { geo in

                ScrollView(.horizontal) {

                    ZStack {

                        IRScrollableTabView.Indicator()
                            .frame(width: self.rects[self.service.selectedIndex].size.width,
                                   height: self.rects[self.service.selectedIndex].size.height)
                            .offset(x: self.offset(width: geo.size.width))
                            .animation(.easeInOut(duration: 0.3))

                        HStack(alignment: .top, spacing: 10) {

                            ForEach(0..<self.service.tabItems.count, id: \.self) { index in

                                TabItemButton(index: index,
                                              isSelected: true,
                                              item: self.service.tabItems[index])
                                    // We want a fixed tab item with.
                                    .frame(width: 70)

                                    // This detects the effective positions of the tabs.
                                    .background(IRTabItemViewSetter(index: index))
                            }
                        }
                            // We want to have the positions within this space.
                            .coordinateSpace(name: "IRReference")

                            // Update the current tab positions.
                            .onPreferenceChange(IRTabItemPreferenceKey.self) { preferences in

                                debugPrint(">>> Preferences:")
                                for p in preferences {

                                    debugPrint(p.rect)
                                    self.rects[p.viewIndex] = p.rect
                                }
                        }
                    }
                }
            }
        }


        private func offset(width: CGFloat) -> CGFloat {

            debugPrint(width)

            let selectedRect = self.rects[self.service.selectedIndex]
            debugPrint(selectedRect)

            let selectedOffset = selectedRect.minX + selectedRect.size.width / 2 - width / 2
            debugPrint(selectedOffset)

            return selectedOffset
        }
    }


    struct Setter: View {


        let index: Int


        var body: some View {

            GeometryReader { geo in

                Rectangle()
                    .fill(Color.clear)
                    .preference(key: IRPreferenceKey.self,
                                value: [IRData(viewIndex: self.index,
                                               rect: geo.frame(in: .named("IRReference")))])
            }
        }
    }


    struct IRPreferenceKey: PreferenceKey {

        typealias Value = [IRData]

        static var defaultValue: [IRScrollableTabView.IRData] = []

        static func reduce(value: inout [IRScrollableTabView.IRData], nextValue: () -> [IRScrollableTabView.IRData]) {

            value.append(contentsOf: nextValue())
        }
    }


    struct IRData: Equatable {

        let viewIndex: Int
        let rect: CGRect
    }

我解决了这个问题!技巧似乎是在
指示器
视图周围放置另一个
GeometryReader
,并获取其宽度以计算偏移量。
.onPreferenceChange
必须附加到
HStack
,而
.coordinateSpace
必须附加到
ZStack
。现在它开始工作了

var body: some View {

            GeometryReader { geo in

                ScrollView(.horizontal) {

                    ZStack {

                        GeometryReader { innerGeo in

                            IRScrollableTabView.Indicator()
                                .frame(width: self.rects[self.service.selectedIndex].size.width,
                                       height: self.rects[self.service.selectedIndex].size.height)
                                .offset(x: self.offset(width: innerGeo.size.width))
                                .animation(.easeInOut(duration: 0.3))
                        }

                        HStack(alignment: .top, spacing: 10) {

                            ForEach(0..<self.service.tabItems.count, id: \.self) { index in

                                TabItemButton(index: index,
                                              isSelected: true,
                                              item: self.service.tabItems[index])
                                    // We want a fixed tab item with.
                                    .frame(width: 70)

                                    // This detects the effective positions of the tabs.
                                    .background(IRTabItemViewSetter(index: index))
                            }
                        }

                            // Update the current tab positions.
                            .onPreferenceChange(IRTabItemPreferenceKey.self) { preferences in

                                debugPrint(">>> Preferences:")
                                for p in preferences {

                                    debugPrint(p.rect)
                                    self.rects[p.viewIndex] = p.rect
                                }
                        }
                    }
                        // We want to have the positions within this space.
                        .coordinateSpace(name: "IRReference")
                }
            }
        }


        private func offset(width: CGFloat) -> CGFloat {

            debugPrint(width)

            let selectedRect = self.rects[self.service.selectedIndex]
            debugPrint(selectedRect)

            let selectedOffset = -width / 2 + CGFloat(80 * self.service.selectedIndex) + selectedRect.size.width / 2
            debugPrint(selectedOffset)

            return selectedOffset
        }
var主体:一些视图{
GeometryReader{geo-in
滚动视图(.horizontal){
ZStack{
GeometryReader{innerGeo in
iScrollableTabView.Indicator()
.frame(宽度:self.rects[self.service.selectedIndex].size.width,
高度:self.rects[self.service.selectedIndex].size.height)
.offset(x:self.offset(宽度:innerGeo.size.width))
.animation(.easeInOut(持续时间:0.3))
}
HStack(对齐:顶部,间距:10){
ForEach(0..CGFloat{
调试打印(宽度)
让selectedRect=self.rects[self.service.selectedIndex]
调试打印(已选择打印)
让selectedOffset=-width/2+CGFloat(80*self.service.selectedIndex)+selectedRect.size.width/2
调试打印(已选择偏移)
返回selectedOffset
}

这是不可测试的。任何演示?短拷贝粘贴运行示例?将所有代码放在下面的要点中:实际上,不是那么短…顺便说一下:我一把GeometryReader放进ScrollView,它就不再滚动了…我不明白为什么…同时我在这里提供了一个带有全功能视图的Swift包:
var body: some View {

            GeometryReader { geo in

                ScrollView(.horizontal) {

                    ZStack {

                        GeometryReader { innerGeo in

                            IRScrollableTabView.Indicator()
                                .frame(width: self.rects[self.service.selectedIndex].size.width,
                                       height: self.rects[self.service.selectedIndex].size.height)
                                .offset(x: self.offset(width: innerGeo.size.width))
                                .animation(.easeInOut(duration: 0.3))
                        }

                        HStack(alignment: .top, spacing: 10) {

                            ForEach(0..<self.service.tabItems.count, id: \.self) { index in

                                TabItemButton(index: index,
                                              isSelected: true,
                                              item: self.service.tabItems[index])
                                    // We want a fixed tab item with.
                                    .frame(width: 70)

                                    // This detects the effective positions of the tabs.
                                    .background(IRTabItemViewSetter(index: index))
                            }
                        }

                            // Update the current tab positions.
                            .onPreferenceChange(IRTabItemPreferenceKey.self) { preferences in

                                debugPrint(">>> Preferences:")
                                for p in preferences {

                                    debugPrint(p.rect)
                                    self.rects[p.viewIndex] = p.rect
                                }
                        }
                    }
                        // We want to have the positions within this space.
                        .coordinateSpace(name: "IRReference")
                }
            }
        }


        private func offset(width: CGFloat) -> CGFloat {

            debugPrint(width)

            let selectedRect = self.rects[self.service.selectedIndex]
            debugPrint(selectedRect)

            let selectedOffset = -width / 2 + CGFloat(80 * self.service.selectedIndex) + selectedRect.size.width / 2
            debugPrint(selectedOffset)

            return selectedOffset
        }