This article covers all aspects of tvOS development, from focus navigation to tvOS App Store Review. We specialize in Apple TV app development. A client came to us with an iOS streaming app: they just wanted to compile it for tvOS. Within an hour of testing on Apple TV, we found that focus navigation didn't work, buttons weren't clickable, the remote didn't respond. The App Store Review rejected the build due to missing parallax effects. A typical scenario: port iOS to tvOS without adaptation is a waste of time. Over 85% of projects that come to us have focus-navigation issues, and only 2 out of 10 iOS apps port successfully without rework. Our team averages 5-6 tvOS projects per year, each ranging from $6,000 to $24,000.
How Focus Navigation tvOS Works
On tvOS, focus navigation tvOS is the primary means of interaction. Users swipe on the remote, and the system moves focus between focusable elements. Focusable items include UIButton, UITextField, and custom UIView with canBecomeFocused = true. Problems start with custom UI: if you don't implement UIFocusEnvironment, focus simply won't reach your elements. SwiftUI tvOS works better — the focusable() modifier and @FocusState manage navigation out of the box. However, LazyVGrid requires explicit focusSection(), otherwise focus jumps unpredictably. According to Apple Human Interface Guidelines, "focus navigation is the primary means of interaction on tvOS".
Parallax effect is a mandatory visual indicator of focus. Users are accustomed to it. Implemented via UIImageView with adjustsImageWhenAncestorFocused = true and layered LSR/LCR format. For SwiftUI, use CardButtonStyle. In our projects, we use a combined approach: SwiftUI for simple screens, UIKit for complex ones. For example, for one VOD client, we implemented a custom overlay based on AVPlayerViewController wrapped via UIViewControllerRepresentable — this preserved the flexibility of real-time content rendering.
Why SwiftUI tvOS Is Better Than UIKit tvOS
SwiftUI tvOS accelerates development of simple screens by 2x but has limitations. Here's a comparison:
| Feature |
UIKit tvOS |
SwiftUI tvOS |
| Focus navigation |
UIFocusEnvironment, canBecomeFocused |
focusable(), @FocusState |
| Parallax effect |
adjustsImageWhenAncestorFocused |
CardButtonStyle (iOS 13+) |
| Video player with custom overlay |
AVPlayerViewController + delegate |
Not supported — need UIViewControllerRepresentable |
| Top Shelf Extension |
TVTopShelfContentProvider (UIKit only) |
No API |
For new projects, we recommend SwiftUI with selective UIKit integration for the player and Top Shelf. This gives development speed and flexibility.
What Is Top Shelf Extension and Why Do You Need It?
Top Shelf Extension allows showing dynamic content on the Apple TV home screen. Implementation via TVTopShelfContentProvider in a separate Extension target. Based on our data, this boosts user engagement by 30%. Typical Top Shelf integration costs between $1,500 and $3,000. We have implemented Top Shelf for 15 projects — none was rejected because of it. App launch time decreases by 20% with Top Shelf, and average user spend increases by 15%.
Common Mistakes When Porting iOS to tvOS
| Mistake |
Consequence |
Solution |
| Missing focus navigation on custom cells |
Focus "gets stuck" |
Implement UIFocusEnvironment or use SwiftUI |
| Ignoring parallax effects |
Visually flat buttons, lower conversion |
Enable adjustsImageWhenAncestorFocused |
| Using standard SwiftUI VideoPlayer without custom video player controls |
Cannot show own control elements |
Wrap AVPlayerViewController via UIViewControllerRepresentable |
| Incorrect handling of remote gestures |
Menu button closes app instead of going back |
Add UISwipeGestureRecognizer and UITapGestureRecognizer with allowedPressTypes |
| Missing parallax effects on all focusable elements |
App Store Review may reject build |
Check all UIImageView for adjustsImageWhenAncestorFocused |
Example Top Shelf Extension configuration
// TVTopShelfContentProvider implementation
class Provider: TVTopShelfContentProvider {
override func loadTopShelfContent(completion: @escaping (TVTopShelfContent?) -> Void) {
let items = // ...
completion(TVTopShelfContent(style: .inset, items: items))
}
}
What's Included in the Work
- Analysis of content model and usage scenarios (VOD, game, utility).
- Navigation design for the remote — a mandatory step.
- Development in SwiftUI + UIKit hybrid stack.
- Integration of Top Shelf, Siri Remote tvOS gestures (
UISwipeGestureRecognizer + UITapGestureRecognizer with allowedPressTypes).
- Apple TV video player setup:
AVPlayerViewController with subtitle support (AVMediaSelectionGroup), chapters (AVTimedMetadataGroup), and Picture-in-Picture using AVPlayer tvOS.
- Testing with physical remote and iPhone as remote via Remote app.
- Publication to App Store via separate tvOS bundle target (or universal build).
- Post-release support.
Development Process
- Analytics — competitor analysis, usability testing of scenarios. Over the years, we've conducted more than 20 audits.
- Design — navigation scheme for the remote, sketches of focus strings.
- Development — weekly iterations, demo on real Apple TV.
- Testing — functional, regression, load testing. 95% of projects pass review on first attempt.
- Deployment — via TestFlight for beta testers, then publication.
Estimated Timelines
- Simple informational or VOD app: 6–10 weeks.
- App with Top Shelf, custom player for Apple TV, and authentication: 10–16 weeks.
- Game or interactive experience — individually.
Development cost varies depending on functionality. Savings when porting from iOS can be up to 40% of the budget. For a typical tvOS app, clients invest between $12,000 and $24,000. Our certified Apple developers guarantee smooth App Store approval. With over 10 years of experience in the Apple ecosystem, we deliver proven results.
Get a consultation for your project — we'll assess complexity and propose the best solution. Contact us to discuss details and order tvOS app development.
Additional resources: read about Siri Remote and Human Interface Guidelines for tvOS.
Development of Widgets, App Clips, and Live Activities: Entry Points Outside the App
We understand that users see your app not only when they open it. A widget on the home screen, a live score in Dynamic Island, a mini experience without installation — these are separate entry points that we implement within platform constraints. Over 5 years, we have developed more than 50 extensions for mobile apps, from simple informational widgets to App Clips with payment scenarios, saving clients up to 30% of time on repeat visits.
What entry points should you consider for your app?
WidgetKit Widget Development: Why You Can't Just "Add a Widget"
WidgetKit works via a Timeline Provider — the widget doesn't stay in memory continuously; it requests data snapshots in advance. The most common mistake: developers try to show real-time data via URLSession directly from getTimeline(). Apple doesn't prohibit this, but with aggressive updates, the system starts throttling requests, and the widget gets stuck on outdated data.
The correct approach: the main app updates data via WidgetCenter.shared.reloadTimelines(ofKind:) — after receiving a push notification or when the user returns to the foreground. The widget reads data from a shared App Group container using UserDefaults(suiteName:) or file storage. No direct network requests in the provider in production.
In the latest iOS versions, AppIntent-based interactive widgets have emerged — buttons and toggles directly on the widget without opening the app. This is implemented via Button(intent:) in the SwiftUI widget layout. Only works for simple actions; complex logic should transition to the app via widgetURL.
How Live Activities Change User Experience?
Live Activities are a mechanism for displaying live data on the Lock Screen and Dynamic Island (iPhone 14 Pro+). They are launched via ActivityKit, updated via push notifications of type liveactivity with a payload up to 4KB.
Architecturally, it's a separate SwiftUI target with two views: compact (Dynamic Island) and expanded (Lock Screen). Data is passed via ActivityAttributes — a strictly typed structure. The dynamic part is ContentState, while the static part (unchanged during the activity) is directly in ActivityAttributes.
A typical issue: Live Activity doesn't update on the device even though push is sent. The reason is that the app doesn't have permission for background push or apns-push-type is set incorrectly. In production, you need apns-push-type: liveactivity and a token from activity.pushToken. According to Apple documentation, without a correct push token, the Activity won't receive updates.
When to Use App Clips vs Instant Apps?
App Clips (iOS) and Instant Apps (Android) solve a similar problem — provide functionality without installing the full app. But the implementation is fundamentally different.
App Clip is a separate target in Xcode, max 15MB, launched via NFC tag, QR code, Safari Smart App Banner, or a link in Messages. Data access is limited: no Keychain sharing with the main app without explicit setup, no access to HealthKit, no push notifications (only ephemeral). The App Clip Card is configured in App Store Connect, and metadata errors are a common reason for rejection.
Android Instant Apps are built on a modular architecture: the app is divided into feature modules, each of which can be downloaded separately via Play Feature Delivery. An Instant App is a feature module with <dist:module dist:instant="true">. The limitation is no more than 15MB total for instant delivery.
Comparison shows that App Clips win in payment scenarios due to Apple Pay integration — conversion is 20% higher compared to Instant Apps in similar cases. Instant Apps are better suited for game demos and services requiring quick access via Google Search.
| Parameter |
App Clips |
Instant Apps |
| Max size |
15 MB |
15 MB |
| Launch triggers |
NFC, QR, URL, Safari |
URL, Google Search, Play Store |
| Shared Keychain |
Via App Group |
Via SharedPreferences/Keystore |
| Recommended scenario |
Payment, boarding, demo |
Game demo, one-time services |
What Does Our Work Include?
-
Audit of current architecture: determine which entry points your app needs — widget, Live Activity, App Clip, Instant App.
-
Prototyping: visual model of the extension following platform guidelines (Apple HIG, Material Design).
-
Development: implementation in Swift (iOS) or Kotlin (Android) using WidgetKit, ActivityKit, App Clip API, Play Feature Delivery.
-
Integration: setting up App Group, Keychain sharing, push certificates, provisioning profiles.
-
Testing: on real devices (iPhone, iPad, Android) and simulators. For Live Activities, test via
xcrun simctl push.
-
Publication: preparing metadata for App Store Connect (App Clip Card) and Google Play Console (Instant App configuration).
-
Documentation and training: architecture description, widget update instructions, push notification troubleshooting.
How Does Our Development Process Work?
-
Analytics: which app features are truly needed outside the app, and which mechanism fits. Widget for forecast — WidgetKit. Real-time delivery tracking — Live Activity. Payment at checkout — App Clip.
-
Design: choosing stack, data update schemes (Timeline, push), UI layouts for compact and expanded views.
-
Implementation: writing code in Swift/Kotlin, configuring App Group, push certificates, test schemes.
-
Testing: each extension is tested in isolation. WidgetKit rendering is verified via Xcode Widget Gallery, Live Activities via simulator with forced push.
-
Deployment: publishing to stores, monitoring metrics (update frequency, App Clip launch count).
Estimated Timeframes
| Extension Type |
Timeframe (business days) |
| Simple informational widget |
5 to 10 |
| Interactive widget (AppIntent) |
10 to 15 |
| Live Activity with push |
10 to 20 |
| App Clip with payment |
20 to 30 |
| Instant App (Android) |
15 to 25 |
Cost is calculated individually after audit. An estimate is provided within 2 business days.
What Are Typical Mistakes in Extension Development?
-
Too frequent widget updates — leads to throttling and empty state. We recommend an interval of at least 15 minutes (see Apple Human Interface Guidelines in WidgetKit documentation).
-
Ignoring shared container — the widget doesn't see data because it uses its own
UserDefaults instead of App Group.
-
Lack of fallback for Live Activities — if push isn't delivered, the user sees outdated data. A periodic polling mechanism via
Activity.update with pushType: nil is needed.
-
Incorrect App Clip Card metadata — a common reason for rejection in App Store Review. For example, incorrect URL or missing icon.
Contact us to assess which extension fits your app. Order an audit of current entry points — we'll find non-obvious scenarios for widgets and App Clips. Get an engineer consultation on architecture today.