Surviving tvOS: An Engineering Log of an Atypical Media Player

In Apple’s vast hardware ecosystem, beyond the “star products” like the iPhone and MacBook that come with their own halos, there are devices that dominate their specific niches yet often fly under the radar of developers. Apple TV is clearly the most representative example of this—its lack of presence often leaves many developers skeptical about the “ROI” of investing time in it.

Due to the cross-platform nature of SwiftUI, I once naively thought that developing a tvOS app was just a simple extension of iOS development. However, over the past two years of communicating with Ronnie Wong, I realized that for most developers, tvOS is a completely new territory full of challenges, whether in terms of design philosophy, hardware limitations, or development and debugging details.

Therefore, I invited Ronnie Wong to share her engineering records from the development of Syncnext. This article not only showcases the unique development scenarios of this “niche” platform within the Apple ecosystem but also summarizes her experience of “surviving” in this field. I hope this authentic perspective will help more developers understand the pains and joys of Apple TV development.

If you are just writing a Demo, tvOS actually looks easy; but as soon as you try to build an App that requires long-term maintenance and faces a real user environment, you will quickly discover: tvOS has never been just a magnified iPad.

It has almost no reliable local persistent storage, and data can be cleared or processes killed by the system at any time; the entire interaction model centers on Focus, with no browser and no WebView, yet at the player level, it deeply relies on the entire Web world. From a design perspective, tvOS is essentially a display terminal, not a workstation.

Syncnext happens to be an atypical example: it is not a custom player serving a single backend, but an attempt to face the real and chaotic network environment on tvOS. This article is not a tutorial, but an engineering log, organizing the design trade-offs and realistic compromises made to “survive” on this platform.

If you are considering making a tvOS App that is more than just a Demo, these experiences might help you avoid some detours.

I. Core Interaction Philosophy: Non-Contact Operation

The UI/UX concept of tvOS is distinct from “touch” systems or “mouse-mode” systems. You need to think using Game UX concepts. On console platforms, the controller interaction model is the primary reference for developing tvOS Apps.

1. Focus Engine vs. SwiftUI @FocusState

In tvOS development, you will encounter two focus systems:

  • Focus Engine: Centered around UIKit, the native solution designed specifically for tvOS.
  • SwiftUI @FocusState: Centered around the entire Apple platform, a cross-platform state management system.

Please note, they are products of completely different concepts. Although the interaction forms are similar, the logic is different.

  • Common Trap: You can use both @FocusState and Focus Engine to highlight two focuses on the UI simultaneously, but the “real focus” recognized by the system is only the instruction of your last operation.

2. Deep Remote Support

tvOS remotes are mainly divided into two generations:

  1. Siri Remote (1st Gen): Touchpad dominant.
  2. Siri Remote (2nd Gen): Physical buttons + Touch ring.

If you don’t care about older users who prefer the old remote, you can simply use standard SwiftUI modifiers. But if you pursue the ultimate experience, you need to use the Game Controller Framework to go deep into the RAW layer to process input signals.

II. System Limits & Survival Guide

1. Harsh Storage Environment

  • No Persistent Storage: You do not have permission to write to the Document directory; you can only write to the Cache folder.
  • Data Loss at Any Time: When tvOS detects that storage space is tight, the system will randomly delete the App’s Cache data. If your App relies on a local SQLite database as its core, this will be catastrophic.
    • Common Phenomenon: “Because your Apple TV ran out of space, the database of this App was deleted.”
    • Suggested Check: Are Aerial screensavers/iCloud Photos enabled? Is the cache for Infuse/SenPlayer set to memory mode?

2. Missing Web Capabilities

  • No Browser: tvOS has no WebView, only a JavaScriptCore framework. Concepts relying on DOM operations cannot be realized here.

3. Network & Device Differences

  • IPv6 Priority: Under the same LAN, a situation may occur where a phone can access the API, but tvOS cannot due to IPv6 strategies.
  • Compute Power Gap: The performance difference between Apple TV 4K models (2017, 2021, 2022) is huge. If you create cool visual effects, please be sure to provide an option to “Turn Off Effects” for older devices.

4. Localization & Troubleshooting

  • No Simplified Chinese Siri: When using Siri voice input, the system will only recognize and output “Traditional Chinese”. If your API requires Simplified Chinese, you must build a “Traditional-Simplified Conversion” system within the App.
  • The Ultimate Solution: If the App fails to launch on some users’ devices (black screen or crash), this is usually not an issue with your code. Please advise the user to “Unplug and Restart” the Apple TV.

III. SwiftUI on tvOS: Practical Workarounds

1. The “Shadow Tactic” for Input Fields

SwiftUI’s TextField on tvOS is both ugly and difficult to use. The solution is the “Shadow Input Method”:

  • UI Layer: Use a beautiful Button to build the UI.
  • Function Layer: Place a transparent CocoaTextField (UIKit) in the view background.
  • Interaction: When the button is clicked, call textField.becomeFirstResponder() to summon the keyboard.

2. Navigation & State Management

  • Searchable Throttling: tvOS triggers search based on input frequency; be sure to implement throttling and cache results.
  • Custom NavigationLink: SwiftUI’s native navigation lacks flexibility. It is recommended to encapsulate a .navigate(using:destination:) modifier to implement “state-driven” navigation transitions.

3. The Necessity of TVUIKit

SwiftUI’s .card style often gives a “knock-off” feel. To develop an App with an official quality, you need to encapsulate TVUIKit components:

  • Typography: Use UILabel for precise line height control; use UITextView for scrolling reading with the remote.
  • Visuals: Use TVPosterView to get native parallax and focus effects.
  • Input: Use TVDigitEntryViewController to implement native numeric passcode input.

4. The Disaster of Mixing Focus Systems

When mixing SwiftUI with UITableView on tvOS, SwiftUI’s focus prediction mechanism often conflicts with the actual height of the list, causing focus misalignment. The solution is usually to take over the Focus system of UITableView directly.

5. Developer Experience Optimization

  • Debug Input: Text cannot be entered efficiently during development. It is recommended to use #if DEBUG macros to wrap dedicated buttons that fill in test keywords with one click.
  • Visual Debugging: Use a custom debugOnlyModifier to highlight the size and position of Views.
  • Further Reading: My Fuck SwiftUI Notes

IV. Player Core Technical Details

1. Silky Progress Bar Interaction

Implemented based on UIPanGestureRecognizer. The touchpad of the TV remote is not a mouse and cannot map displacement directly.

  • Virtual Damping: Set a proportional coefficient of location / 5. This “feel value” scales the displacement non-linearly, ensuring precise positioning down to the second even in long videos.
  • Real-time Feedback: The status must drive the UI Overlay directly during sliding to achieve “What You See Is What You Get”.

2. Dual UI Overlay & Back Button Logic

The logic of the Menu (Back) button is multi-dimensional and requires a state machine:

  1. State A (UI Showing): If focus is on a button, press Menu -> Focus returns to the progress bar.
  2. State B (Focus on Progress Bar): Press Menu -> Hide UI.
  3. State C (UI Hidden): Press Menu -> Exit playback.

3. AVPlayer Tuning

AVPlayer is a “brute force” network player by default and needs targeted tuning:

  • Reduce Latency:
Swift
player?.automaticallyWaitsToMinimizeStalling = true
player?.currentItem?.preferredForwardBufferDuration = 0 // Recommended for Live/Stream
  • Local File Specialization:
Swift
// If local file
automaticallyWaitsToMinimizeStalling = false
preferredForwardBufferDuration = 1
  • HLS Support: AVPlayer mandates HTTP(s) protocols. If you need to dynamically rebuild m3u8, you must use a Local Server (Recommended framework: Swifter).
  • Dynamic Background: If using AVPlayer for dynamic backgrounds, be sure to set AVAudioSession to .ambient to avoid interrupting system audio.

4. Custom Dismiss Behavior

In newer versions of tvOS, AVPlayer’s response to dismiss has changed. It is recommended to rewrite the dismiss logic, checking in order: presentingViewController (dismiss) -> navigationController (pop) -> removeFromParent.

V. Data Synchronization Strategy

From the perspective of 2026, I do not recommend using the CoreData with CloudKit solution. It has never solved the problem of Space Inflation, and the risk is extremely high on tvOS, which lacks persistent storage.

Fatbobman’s Note

The “space inflation” mentioned here refers to the fact that when local SQLite data is synchronized to CloudKit via NSPersistentCloudKitContainer, the actual storage space occupied in the cloud is often multiples of the local usage due to underlying type conversion, system auxiliary fields, and entity design factors. This discrepancy is significantly amplified in scenarios containing large amounts of binary data, which can easily and rapidly exhaust the user’s iCloud storage quota.

For a detailed discussion on this issue, please refer to the Discord records from that time.

I recommend using sqlite-data as an iCloud support solution. It has been tested in the TracklyReborn project and performs stably and controllably.

VI. Postscript: About eisonAI

Before ending this hardcore tvOS engineering record, I want to introduce my latest work — eisonAI.

iPhone-Medata-Preview.jpg

This is an App about “Memory” and “Flow”. We often encounter situations where we can’t find things we’ve seen, or our thoughts get messy as soon as we write them down. eisonAI introduces the concept of Cognitive Index™: it remembers not just “content”, but “utility”. It acts like a librarian in your brain, automatically filing inspiration, background data, and citations, protecting your thought flow from being interrupted.

About Author

Ronnie Wong is an independent developer who has long been conducting system-level and product-level development on Apple platforms (iOS / macOS / tvOS), focusing on player architecture, system boundary issues, local-first toolchains, and AI automation pipelines. She has developed Syncnext (a universal player for tvOS), aDict, TracklyReborn, eisonAI, and multiple local AI automation systems (such as HLN Machine). Her recent work focuses on LLM toolchains, automated content production pipelines, and engineering practices related to Semantic Entropy. This article is compiled from years of practical experience with Syncnext on tvOS.

GitHub: https://github.com/qoli

Subscribe to Fatbobman

Weekly Swift & SwiftUI highlights. Join developers.

Subscribe Now