4

Background: I'm converting a project from UIKit to SwiftUI. Currently with UIKit, I have a UICollectionView within a UICollectionView, both with Paging enabled. This allows users to swipe up/down and left/right on cells. Each cell fills the entire screen (similar to TikTok's main UI) and each cell has a lot of data to pull from Firebase, so I do not want to load all items when the first view appears, but rather as each cell will appear (this is the current implementation under UIKit via UICollectionView delegates).

Problem: I can't find a way to enable this "paging" (based on user swiping) in SwiftUI. It's hard to imagine that Apple made this new framework without a Paging feature? If they haven't included this, is there a workaround? Otherwise, is there an easy way to include a UICollectionView into a SwiftUIView?

Any help or workarounds would be nice! Thanks :)

nicksarno
  • 3,850
  • 1
  • 13
  • 33

2 Answers2

2

Horizontal/Vertical paging in iOS 17

From iOS 17, you can enable the paging behavior by applying the following modifier on the ScrollView:

.scrollTargetBehavior(.paging)

This works for b both horizontal and vertical ScrollViews. You can use .containerRelativeFrame(...) on the content to make it size as the visible area of the scrollView:

Demo:
ScrollView(.horizontal) {
    LazyHStack {
        ForEach((1...100), id: \.self) { index in
            Text("\(index)")
                .containerRelativeFrame(.horizontal) //  Makes it mathes parent's visible width
                .containerRelativeFrame(.vertical) //  Makes it mathes parent's visible height
        }
    }
}
.scrollTargetBehavior(.paging) //  Enables paging
Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
1

SwiftUI 2

It looks like a LazyGrid may be what you need.

Here is a sample code for a vertical grid:

struct ContentView: View {
    let data = (1...1000).map { "Item \($0)" }

    let columns = [
        GridItem(.adaptive(minimum: 80))
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(data, id: \.self) { item in
                    Text(item)
                }
            }
            .padding(.horizontal)
        }
    }
}

You can find more information in these links:


SwiftUI 1

If you're bound to iOS 13 or LazyGrid just doesn't match your expectations, you can still wrap your UICollectionView in UIViewRepresentable.

See this link for more explanation: Implementing UICollectionView / UICollectionView DiffableDataSource in SwiftUI

pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • Thanks!! I will try implementing this later this week. I was running Xcode 11.6 (doesn't have LazyGrid), but I just downloaded the Xcode 12.0 beta and see it now! However, it's my understanding that we can't actually deploy builds from Xcode while it is in Beta. Do you have any idea when we would expect Xcode 12.0 to be usable to actually push a version to the App Store? I'm fairly new to this and just want to be cautious of building on a Beta version and having to wait months to deploy it to the App Store? – nicksarno Aug 10 '20 at 23:25
  • 1
    @purebreadd Nothing official yet, probably in fall. Just note that SwiftUI 2.0 is available on iOS14+ and SwiftUI 1.0 on iOS13+. It's better to know this *before* rewriting your app in SwiftUI. – pawello2222 Aug 11 '20 at 07:01
  • 1
    Hi again, I'm working with the GridItems now and I got the lazy cells in place. However, the ScrollViews are smooth scrolling only and not paging. Any idea how to enable "Paging" on them? The UICollectionView used to have a nice little delegate :( – nicksarno Aug 12 '20 at 15:14
  • @purebreadd You can take a look at this answer on [How can I implement PageView in SwiftUI?](https://stackoverflow.com/a/63159912/8697793) But note that SwiftUI is still new - it might not have everything you need. And it might be better to stick with UIKit for now if you're using more complex views. – pawello2222 Aug 12 '20 at 15:15
  • Thanks again. I saw this, but I need it to work on the LazyGrids. I believe TabView loads all views at once :( – nicksarno Aug 12 '20 at 15:32
  • 1
    @purebreadd You can use UIViewRepresentable to integrate UIKit into SwiftUI. See [Implementing UICollectionView / UICollectionView DiffableDataSource in SwiftUI](https://www.averyvine.com/blog/programming/2019/06/07/uicollectionview-and-uicollectionviewdiffabledatasource-in-swiftui). I updated my answer as well. – pawello2222 Aug 12 '20 at 15:38
  • That looks like the best bet lol gunna play around with it now – nicksarno Aug 12 '20 at 15:39
  • 1
    If you're curious, I went through the process to convert UICollectionView into SwiftUI and to incorporate all the custom sizing / cells in our app, it turned into a headache. This approach is probably best with simple UICollectionViews. Anyway, our solution for now is to use the old project (to keep the functionality on the screens that have complex CollecionViews) and only updating screens that don't need this functionality to SwiftUI. Thanks again for the help :) – nicksarno Aug 12 '20 at 18:51