SwiftUI / PreferenceKey: как избежать перемещения ректов при прокрутке? - PullRequest
1 голос
/ 18 марта 2020

Я работаю над панелью вкладок с возможностью прокрутки и движущимся фоном для выбранной вкладки.

Решение основано на PreferenceKey s; Тем не менее, у меня есть проблема, чтобы получить стабильный движущийся фон по отношению к вкладкам. В настоящее время он перемещается при прокрутке, что нежелательно; вместо этого он должен быть зафиксирован по отношению к элементу вкладки и прокручивать их.

Почему это так и как этого избежать? При удалении ScrollView фон перемещается правильно к выбранному элементу вкладки. TabItemButton - это просто Button с какой-то специальной меткой.

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
    }

Служба определяется следующим образом (т. Е. Ничего особенного ...):

final class Service: ObservableObject {


        @Published var currentDestinationView: AnyView
        @Published var tabItems: [IRScrollableTabView.Item]
        @Published var selectedIndex: Int { didSet { debugPrint("selectedIndex: \(selectedIndex)") } }


        init(initialDestinationView: AnyView,
             tabItems: [IRScrollableTabView.Item],
             initialSelectedIndex: Int) {

            self.currentDestinationView = initialDestinationView
            self.tabItems = tabItems
            self.selectedIndex = initialSelectedIndex
        }
    }

    struct Item: Identifiable {

        var id: UUID = UUID()

        var title: String
        var image: Image = Image(systemName: "circle")
    }

1 Ответ

0 голосов
/ 18 марта 2020

Я решил проблему! Хитрость, казалось, заключалась в том, чтобы поместить еще один GeometryReader вокруг Indicator вида и взять его ширину для вычисления смещения. .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
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...