Book

Exploring Freelancing

Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!

In iOS 14, SwiftUI introduced the PageTabViewStyle for implementing a paged scrolling TabView. This helps you easily create a carousel-like view that you can use in the onboarding or the header view similar to what we have in the App Store app.

For tvOS, after some experimentation, I realized that the .page does give you the page control but does not provide with those extra little sides of the secondary views. Those usually provide the experience that there are other scrollable views on the left and the right. So, if you want to hide the page control, how do you know it is horizontally scrollable?

You use the ScrollView with the axis as horizontal.

This post explores both the methods of creating a carousel view.

TabView and PageTabViewStyle

You can put your list of views inside a TabView and add the tab view style as page. It automatically provides you with a carousel view with an embedded page control.

struct HomeView: View {
  @StateObject private var viewModel = QuotesViewModel()

  var body: some View {
    GeometryReader { geometry in
      let height = geometry.size.height

      if !viewModel.quotesList.isEmpty {
        TabView {
          ForEach(viewModel.quotesList) { quote in
            QuoteCarouselView(quote: quote)
              .focusable()
          }
        }
        .tabViewStyle(.page)
        .frame(maxHeight: height * 0.65)
      }
    }
  }
}

One thing to note is that the TabView does not seem to work lazily and loads all the views at once. So, you are fine with TabView if there are a few views inside it.

Otherwise, if you want to create an infinite carousel, let’s look at another alternative.

ScrollView and LazyHGrid

You can use the ScrollView with LazyHGrid to get the little side views without the page control. This option is given in the session Build SwiftUI Apps for tvOS. Although, in the example given, you have to figure out the frame on your own.

Using some tiny magic of GeometryReader, you can provide the maximum height to the whole scroll view and maximize the width of each carousel view displayed on the screen.

Providing a width is necessary because the default is infinite in a horizontal scroll view without the width.

struct HomeView: View {
  @StateObject private var viewModel = QuotesViewModel()

  var body: some View {
    GeometryReader { geometry in
      let height = geometry.size.height
      let width = geometry.size.width

      ScrollView(.horizontal) {
        LazyHGrid(rows: [.init()]) {
          ForEach(viewModel.quotesList) { quote in
            QuoteCarouselView(quote: quote)
              .focusable()
              .frame(width: width)
          }
        }
      }
      .frame(maxHeight: height * 0.65)
    }
  }
}

Running the app, we get the following beautiful carousel view:

Conclusion

While the initial impressions with SwiftUI’s support for tvOS haven’t been great, I am still looking forward to experimenting on the large screen.

Tag @rudrankriyam on Twitter if you have experience working with tvOS and want to spread some knowledge around!

Thanks for reading, and I hope you’re enjoying it!

Book

Exploring Freelancing

Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!