Untitled
unknown
plain_text
2 years ago
4.9 kB
6
Indexable
// // LoopingScrollView.swift // InfiniteLoopingScrollView // // Created by Ruslan Magomedov on 12.12.2023. // import SwiftUI struct LoopingScrollView<Content: View, Item: RandomAccessCollection>: View where Item.Element: Identifiable { //Ширина элементов внутри ScrollView var width: CGFloat //Высота элементов внутри ScrollView var height: CGFloat //Промежуток между элементами var spacing: CGFloat = 0 //Коллекция данных, которую нужно отобразить var items: Item //Флаг, определяющий, должны ли отображаться индикаторы прокрутки var showsIndicators = false var pagingEffect = true //Замыкание, которое принимает элемент коллекции и возвращает View для его отображения @ViewBuilder var content: (Item.Element) -> Content var body: some View { GeometryReader { let size = $0.size let repeatingCount = width > 0 ? Int((size.width / width).rounded()) + 1 : 1 ScrollView(.horizontal, showsIndicators: showsIndicators) { LazyHStack(spacing: spacing) { ForEach(items) { item in content(item) .frame(width: width, height: height) } ForEach(0..<repeatingCount, id: \.self) { index in let item = Array(items)[index % items.count] content(item) .frame(width: width, height: height) } } .background { ScrollViewHelper( width: width, spacing: spacing, itemsCount: items.count, repeatingCount: repeatingCount ) } } .frame(height: height) } } } // Внутренний UIViewRepresentable используется для добавления UIScrollViewDelegate // к ScrollView, чтобы обеспечить бесконечную прокрутку fileprivate struct ScrollViewHelper: UIViewRepresentable { var width: CGFloat var spacing: CGFloat var itemsCount: Int var repeatingCount: Int //Создаёт объект Coordinator func makeCoordinator() -> Coordinator { return Coordinator( width: width, spacing: spacing, itemsCount: itemsCount, repeatingCount: repeatingCount ) } //Создаёт пустой UIView func makeUIView(context: Context) -> UIView { return UIView() } //Настраиваем и обновляем пораметры координатора func updateUIView(_ uiView: UIView, context: Context) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.06) { if let scrollView = uiView.superview?.superview?.superview as? UIScrollView, !context.coordinator.isAdded { scrollView.delegate = context.coordinator context.coordinator.isAdded = true } } context.coordinator.width = width context.coordinator.spacing = spacing context.coordinator.itemsCount = itemsCount context.coordinator.repeatingCount = repeatingCount } class Coordinator: NSObject, UIScrollViewDelegate { var width: CGFloat var spacing: CGFloat var itemsCount: Int var repeatingCount: Int init(width: CGFloat, spacing: CGFloat, itemsCount: Int, repeatingCount: Int) { self.width = width self.spacing = spacing self.itemsCount = itemsCount self.repeatingCount = repeatingCount } var isAdded: Bool = false func scrollViewDidScroll(_ scrollView: UIScrollView) { guard itemsCount > 0 else { return } let minX = scrollView.contentOffset.x let mainContentSize = CGFloat(itemsCount) * width let spacingSize = CGFloat(itemsCount) * spacing if minX > (mainContentSize + spacingSize) { scrollView.contentOffset.x -= (mainContentSize + spacingSize) } if minX < 0 { scrollView.contentOffset.x += (mainContentSize + spacingSize) } } } } #Preview { ContentView() }
Editor is loading...
Leave a Comment