Untitled
unknown
plain_text
2 years ago
4.9 kB
9
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