我尝试了您的代码,但无法使用 List OR ScrollView 修复任何问题,但可以作为无限滚动的 uiscrollview。
1.在 UIViewRepresentable 中包装 uiscrollView
struct ScrollViewWrapper: UIViewRepresentable {
private let uiScrollView: UIInfiniteScrollView
init<Content: View>(content: Content) {
uiScrollView = UIInfiniteScrollView()
}
init<Content: View>(@ViewBuilder content: () -> Content) {
self.init(content: content())
}
func makeUIView(context: Context) -> UIScrollView {
return uiScrollView
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
}
}
2.这是我无限滚动uiscrollview的全部代码
class UIInfiniteScrollView: UIScrollView {
private enum Placement {
case top
case bottom
}
var months: [Date] {
return Calendar.current.generateDates(inside: Calendar.current.dateInterval(of: .year, for: Date())!, matching: DateComponents(day: 1, hour: 0, minute: 0, second: 0))
}
var visibleViews: [UIView] = []
var container: UIView! = nil
var visibleDates: [Date] = [Date()]
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: (*) otherwise can cause a bug of infinite scroll
func setup() {
contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 6)
scrollsToTop = false // (*)
showsVerticalScrollIndicator = false
container = UIView(frame: CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height))
container.backgroundColor = .purple
addSubview(container)
}
override func layoutSubviews() {
super.layoutSubviews()
recenterIfNecessary()
placeViews(min: bounds.minY, max: bounds.maxY)
}
func recenterIfNecessary() {
let currentOffset = contentOffset
let contentHeight = contentSize.height
let centerOffsetY = (contentHeight - bounds.size.height) / 2.0
let distanceFromCenter = abs(contentOffset.y - centerOffsetY)
if distanceFromCenter > contentHeight / 3.0 {
contentOffset = CGPoint(x: currentOffset.x, y: centerOffsetY)
visibleViews.forEach { v in
v.center = CGPoint(x: v.center.x, y: v.center.y + (centerOffsetY - currentOffset.y))
}
}
}
func placeViews(min: CGFloat, max: CGFloat) {
// first run
if visibleViews.count == 0 {
_ = place(on: .bottom, edge: min)
}
// place on top
var topEdge: CGFloat = visibleViews.first!.frame.minY
while topEdge > min {topEdge = place(on: .top, edge: topEdge)}
// place on bottom
var bottomEdge: CGFloat = visibleViews.last!.frame.maxY
while bottomEdge < max {bottomEdge = place(on: .bottom, edge: bottomEdge)}
// remove invisible items
var last = visibleViews.last
while (last?.frame.minY ?? max) > max {
last?.removeFromSuperview()
visibleViews.removeLast()
visibleDates.removeLast()
last = visibleViews.last
}
var first = visibleViews.first
while (first?.frame.maxY ?? min) < min {
first?.removeFromSuperview()
visibleViews.removeFirst()
visibleDates.removeFirst()
first = visibleViews.first
}
}
//MARK: returns the new edge either biggest or smallest
private func place(on: Placement, edge: CGFloat) -> CGFloat {
switch on {
case .top:
let newDate = Calendar.current.date(byAdding: .month, value: -1, to: visibleDates.first ?? Date())!
let newMonth = makeUIViewMonth(newDate)
visibleViews.insert(newMonth, at: 0)
visibleDates.insert(newDate, at: 0)
container.addSubview(newMonth)
newMonth.frame.origin.y = edge - newMonth.frame.size.height
return newMonth.frame.minY
case .bottom:
let newDate = Calendar.current.date(byAdding: .month, value: 1, to: visibleDates.last ?? Date())!
let newMonth = makeUIViewMonth(newDate)
visibleViews.append(newMonth)
visibleDates.append(newDate)
container.addSubview(newMonth)
newMonth.frame.origin.y = edge
return newMonth.frame.maxY
}
}
func makeUIViewMonth(_ date: Date) -> UIView {
let month = makeSwiftUIMonth(from: date)
let hosting = UIHostingController(rootView: month)
hosting.view.bounds.size = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 0.55)
hosting.view.clipsToBounds = true
hosting.view.center.x = container.center.x
return hosting.view
}
func makeSwiftUIMonth(from date: Date) -> some View {
return MonthView(month: date) { day in
Text(String(Calendar.current.component(.day, from: day)))
}
}
}
仔细观察那个,它几乎不言自明,取自 WWDC 2011 的想法,当你足够靠近边缘时,你将偏移重置为屏幕中间,这一切都归结为平铺你的视图,所以它们都出现在顶部彼此的。如果您想对该课程进行任何澄清,请在评论中提问。当你弄清楚这两个时,你就可以粘合同样在提供的类中的 SwiftUIView。目前在屏幕上看到视图的唯一方法是为hosting.view指定一个显式大小,如果你知道如何让SwiftUIView调整hosting.view的大小,请在评论中告诉我,我正在寻找一个答案。希望代码对某人有所帮助,如果有问题请发表评论。