Building a Netflix-like app for iOS and iPadOS in #SwiftUI - a thread:
Two weeks ago, I wrote about my experience with building a video streaming app for @cineasterna ( https://twitter.com/danielsaidi/status/1335146915428786181). The app lets people watch movies for free with their public library card. This thread will discuss how porting that app to a universal #SwiftUI app went.
Just like on tvOS, the iOS and iPadOS app (referred to as iOS from now on) is oriented around four tabs - Discover, All Movies, Search and Profile. If a logged in user has favorites, an additional Favorites tab is added. If no user is logged in, the Profile tab says Login.
The Discover screen is a vertical list with horizontal shelves. This caused me much headache in tvOS, where lazy stacks and grids have horrible performance. This is not true for iOS, though, where performance is amazing, so here I use stacks instead of a custom collection view.
Movie covers are downloaded with Kingfisher, with the same configuration as the TV. I use a pre-processor that scales down images and use a disk cache. However, the stacks and grids put less constraints on image sizes than the UIKit collection view wrapper I had to use for tvOS.
Shelf sections are tappable and takes you to the specific list. Since I use stacks and grids instead of the UIKit collection view wrapper from tvOS, I can use navigation links for movie covers. However, due to the app design, I chose to present movies in modal sheets.
Navigating to a list renders it as a LazyVGrid. Here, I had layout problems, since I want to 3 covers per row for phones. This will obviously not work for larger screens, so I chose a size range instead. It looks good even on large screens, but there covers should be larger.
Both shelves and grids lazy loads more content when they scroll to the end. This was easy to do, by looking at the movie when rendering a list item. For shelves, the movie must be the first movie in the last loaded list. For grids, it must be the last loaded movie in the list.
”All Movies” loads a first, unfiltered page into a lazy grid and lazy loads more content as the user scrolls. Since this is a vast data source, I added filtering options topmost. The filtering is done in custom (simple) pickers that supports optionals and multi selection.
”Search” has a custom-made header that replicates a native search bar, including an embedded clear button and a trailing Cancel button. Just like ”All Movies”, it presents the search result in a lazy grid and lazy loads more content as the user scrolls.
I adapted the movie screen from tvOS to work as a modal sheet for smaller screens. Instead of a backdrop, the movie image is now presented as a prominent header, with the most important info and some action buttons. Below, more info is displayed, together with primary actions.
The contributor list was a breeze to build in #SwiftUI, using a scrolling HStack and a clip shape. I can’t even begin to imagine building it with a UICollectionView in UIKit. Woooops, looking at it now, I see that I accidentally added it to a padded container. Hotfix coming up

The video player was easy to build, by just reusing the player from the TV app and presenting it as a full screen cover. The player stores the position of each unique movie and restores it the next time that movie is played. Reaching the end resets position and closes the player.
A fun addition to this app was to build Chromecast support with the GoogleCast library. All in all, the Chromecast docs are great, the Swift sample code not swifty, the sample app badly focused at the core aspects of Chromecast and the overall developer experience not that great.
Finally, the Profile screen is limited in design and functionality. It lets the user login, logout, switch library and get more information about the service. It also has links to support and account pages.
To wrap up, building this app in #SwiftUI was a lot easier than to build one for tvOS, much since I know the HIG better and that grids and stacks work better. Some views and api:s are still missing, so you still have to wrap native UIKit components, but not as much as in tvOS.
All in all, this was another fun project that I'm proud to release. @cineasterna is available on iOS and tvOS, with macOS support for M1 Macs running the iOS app. To test it, search for Cineasterna on the App Store (loans requires a Swedish library card). Thanks for reading!
