UIHostingConfiguration on tvOS: focus permanently broken with multiple focusable SwiftUI views
Hi everyone,
I'm working on a tvOS app with a UICollectionView. Some cells embed SwiftUI content via UIHostingConfiguration, specifically a row of 3 buttons that should be individually focusable. The cell itself returns canBecomeFocused = false so focus passes through to the SwiftUI buttons.
The problem: after navigating focus into that section once, it becomes permanently unfocusable. Focus enters briefly, then immediately exits to nil on its own, without any user input. From that point on, the focus engine completely skips the section.
The exact same SwiftUI view works perfectly when embedded via UIHostingController instead.
How to reproduce
Press DOWN to move focus into the UIHostingConfiguration section
Focus lands on a SwiftUI button for a split second
Focus exits on its own and bumps to another section
The section is now dead, focus skips it on every subsequent navigation
What the system logs say (-UIFocusLoggingEnabled YES)
Right when focus enters, the system reports the SwiftUI focus items as "disappearing":
Ignoring focus update request for disappearing focus environment <UIKitFocusSectionResponderItem>
Then when searching for a new focusable item:
<SwiftUI._UIInheritedView> → (warning) No focusable items found.
<UIHostingContentView> → (warning) No focusable items found.
=== unable to find focused item in context. retrying with updated request. ===
The views are still in the hierarchy (verified by pointer), but the UIHostingContentView no longer exposes its virtual focus items. I also see mismatched parentFocusEnvironment on those items, pointing to a _UIHostingView from a completely different cell.
What I've tried
I've spent a lot of time on this with my colleagues, dug through the very limited documentation available online, and even used AI agents to help brainstorm. We tested 10 different approaches, none worked:
Overriding preferredFocusEnvironments to point to the UIHostingContentView
setNeedsFocusUpdate() / updateFocusIfNeeded(), rescan finds nothing
Forcing UIKit redraws (setNeedsLayout, setNeedsDisplay)
Removing .focusSection()
Removing all SwiftUI animations, identical behavior
Using canFocusItemAt: delegate instead of cell subclass, identical
remembersLastFocusedIndexPath = true, causes a separate focus trap
configurationUpdateHandler + setNeedsUpdateConfiguration(), config is rebuilt but virtual items stay deregistered
Verified the UIHostingContentView never leaves the hierarchy. It doesn't, its internal state is just corrupted
My workaround
I switched to UIHostingController with proper view controller containment. It works because the hosting controller is a full UIFocusEnvironment, so the focus engine can traverse it and it correctly maintains its virtual items.
Has anyone encountered this? Is there a known pattern for using UIHostingConfiguration on tvOS with multiple focusable SwiftUI elements? Or should I just file a Feedback?
Thanks for any help!
Focus
RSS for tagAdjust your app’s behavior and filter incoming notifications based on the user's Focus settings.
Posts under Focus tag
10 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I have recently added a SetFocusFilterIntent target extension to my app which is a system utility which goes into the menu bar(Application is agent = YES). I have followed the approach in the WWDC22 video introducing Focus Intent and I have created an App Groups to being able to make the Extension to communicate with my main app, however from when I did this sometimes when I run the app I do get this log line:
Couldn't read values in CFPrefsPlistSource<0x97cd34700> (Domain: group.xxx.xxx.MyApp, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd
Despite this the Focus mode integration is working correctly on my development Mac. However I used to Archive the app and then Copy the app to my MacBook but when I do that now my other Mac cannot open the app and it is giving me an error. If I revert this change then I can bring the app back to my other Mac as usual following the procedure: Product -> Archive.
Then from the archiver: Distribute App -> Copy App. After that I copy the app generated to the App folder of my other MacBook but it doesn't open anymore.
During the archival phase now I am even getting this warning:
MyAppFocus.appex is an ExtensionKit extension and must be embedded in the parent app bundle's Extensions directory, but is embedded in the parent app bundle's ../../../BuildProductsPath/Release/MyApp.app/Contents/Extensions directory.
How can I solve this issue?
If I rollback the commit related to this SetFocusFilterIntent new feature the app can be Copied and moved to the other Mac as before.
Is this related to the extension or to the fact that I had to use this new entitlement: com.apple.security.application-groups ?
Dear Apple Team,
I hope this message finds you well. I wanted to share a playful and innovative idea that could enhance the iPhone experience—particularly when viewing content in full-screen mode through apps like Apple TV or YouTube.
Feature Concept: Hands-Free Dismissal of Notifications
When the iPhone is in landscape mode, incoming notifications can interrupt the viewing experience. While Focus Mode and swipe gestures help, I thought of a more intuitive and hands-free interaction: using a light puff of air directed toward the screen to dismiss a notification.
This interaction could use the microphone or other onboard sensors to detect a brief burst of air, providing a fun and natural way to maintain immersion without touching the device.
If this isn’t feasible with current hardware, here are a few alternative concepts that align with the same goal:
Blink to Dismiss: Using Face ID sensors to detect a quick blink as a hands-free gesture.
Shake to Dismiss: A gentle shake gesture when holding the iPhone in one hand.
Gaze-Based Dismissal: Notifications automatically disappear after a brief moment of eye contact.
These ideas could offer both accessibility benefits and a touch of delight—making the iPhone feel even more magical and responsive.
Thank you for your time and for considering this suggestion!
Warm regards,
Badhan Baidya
I’m trying to customize the keyboard focus appearance in SwiftUI.
In UIKit (see WWDC 2021 session Focus on iPad keyboard navigation), it’s possible to remove the default UIFocusHaloEffect and change a view’s appearance depending on whether it has focus or not.
In SwiftUI I’ve tried the following:
.focusable() // .focusable(true, interactions: .activate)
.focusEffectDisabled()
.focused($isFocused)
However, I’m running into several issues:
.focusable(true, interactions: .activate) causes an infinite loop, so keyboard navigation stops responding
.focusEffectDisabled() doesn’t seem to remove the default focus effect on iOS
Using @FocusState prevents Space from triggering the action when the view has keyboard focus
My main questions:
How can I reliably detect whether a SwiftUI view has keyboard focus? (Is there an alternative to FocusState that integrates better with keyboard navigation on iOS?)
What’s the recommended way in SwiftUI to disable the default focus effect (the blue overlay) and replace it with a custom border?
Any guidance or best practices would be greatly appreciated!
Here's my sample code:
import SwiftUI
struct KeyboardFocusExample: View {
var body: some View {
// The ScrollView is required, otherwise the custom focus value resets to false after a few seconds. I also need it for my actual use case
ScrollView {
VStack {
Text("First button")
.keyboardFocus()
.button {
print("First button tapped")
}
Text("Second button")
.keyboardFocus()
.button {
print("Second button tapped")
}
}
}
}
}
// MARK: - Focus Modifier
struct KeyboardFocusModifier: ViewModifier {
@FocusState private var isFocused: Bool
func body(content: Content) -> some View {
content
.focusable() // ⚠️ Must come before .focused(), otherwise the FocusState won’t be recognized
// .focusable(true, interactions: .activate) // ⚠️ This causes an infinite loop, so keyboard navigation no longer responds
.focusEffectDisabled() // ⚠️ Has no effect on iOS
.focused($isFocused)
// Custom Halo effect
.padding(4)
.overlay(
RoundedRectangle(cornerRadius: 18)
.strokeBorder(
isFocused ? .red : .clear,
lineWidth: 2
)
)
.padding(-4)
}
}
extension View {
public func keyboardFocus() -> some View {
modifier(KeyboardFocusModifier())
}
}
// MARK: - Button Modifier
/// ⚠️ Using a Button view makes no difference
struct ButtonModifier: ViewModifier {
let action: () -> Void
func body(content: Content) -> some View {
content
.contentShape(Rectangle())
.onTapGesture {
action()
}
.accessibilityAction {
action()
}
.accessibilityAddTraits(.isButton)
.accessibilityElement(children: .combine)
.accessibilityRespondsToUserInteraction()
}
}
extension View {
public func button(action: @escaping () -> Void) -> some View {
modifier(ButtonModifier(action: action))
}
}
Hello dear community!
I'm still new to SwiftUI and going through the official Introducing SwiftUI Tutorials (basically, building the Landmarks app)
And I'm struggle with some behavior I noticed in the macOS version of the Landmarks app.
So, here is the idea, there is a list of Landmarks (the simplified version from the tutorial):
struct LandmarkList: View {
@Environment(ModelData.self) var modelData
@State private var selectedLandmark: Landmark?
var index: Int? {
modelData.landmarks.firstIndex(where: { $0.id == selectedLandmark?.id })
}
var body: some View {
@Bindable var modelData = modelData
NavigationSplitView {
List(selection: $selectedLandmark) {
ForEach(modelData.landmarks) { landmark in
NavigationLink {
LandmarkDetail(landmark: landmark)
} label: {
LandmarkRow(landmark: landmark)
}
.tag(landmark)
}
}
.navigationTitle("Landmarks")
.frame(minWidth: 300)
} detail: {
Text("Select a landmark")
}
.focusedValue(\.selectedLandmark, $modelData.landmarks[index ?? 0])
}
}
And there are a few helper structs which makes the possibility of the Marking a selected landmark as favorite (or remove) via shortcut and via menu:
struct LandmarkCommands: Commands {
@FocusedBinding(\.selectedLandmark) var selectedLandmark
var body: some Commands {
SidebarCommands()
CommandMenu("Landmark") {
Button("\(selectedLandmark?.isFavorite == true ? "Remove" : "Mark") as Favorite") {
selectedLandmark?.isFavorite.toggle()
}
.keyboardShortcut("f", modifiers: [.shift, .option])
.disabled(selectedLandmark == nil)
}
}
}
private struct SelectedLandmarkKey: FocusedValueKey {
typealias Value = Binding<Landmark>
}
extension FocusedValues {
var selectedLandmark: Binding<Landmark>? {
get {
self[SelectedLandmarkKey.self]
} set {
self[SelectedLandmarkKey.self] = newValue
}
}
}
So, with this setup which is presented in the tutorial I notice 3 issues:
On the first launch of the app, if I try to select a landmark — it's get unselected instantly. It I try to select it again — it works
Marking a selected Landmark as favorite via shortcut (f+shift+opt) or via menu makes the selected Landmark unselected
On the Landmark details — marking a Landmark as Favorite also makes the landmark unselected.
You can check it on your own if you download the completed project from this page: https://developer.apple.com/tutorials/swiftui/creating-a-macos-app
But could someone please explain why it's happening? And how to avoid such a bad UX with unselecting items in the list?
My app Balletrax is a music player for people to use while they teach ballet. Used to be you could silence notifications during use, but now the customer seems to have to know how to use Focus mode, remember to turn it on and off, and have to check the notifications one does and doesn't want to use. Is there no way to silence all notifications when the app is in use?
I am developing a vision os app for controlling an underwater ROV. I have ornaments with telemetry and buttons around a central video view feed. I have custom buttons mappings, such as "A" for locking the depth of the drone. However, when I look at buttons or certain ornaments, my custom gamepad logic is kept from running. This means that when a SwiftUI Button gains focus on visionOS, pressing the controller’s A button triggers the system’s default “click” on that Button rather than my custom buttonA handler. Essentially, focus interception by the system is stealing my A-press events and preventing my custom gamepad logic from running.
Is there a way to disable the built in gamepad interaction and only allow my custom gamepad mappings?
Topic:
Accessibility & Inclusion
SubTopic:
General
Tags:
Game Controller
Accessibility
Focus
visionOS
I’ve set up a focus filter, but the perform() method in SetFocusFilterIntent isn't called when the focus mode is toggled on or off on my iPhone since I updated to iOS 18 beta (22A5326f).
I can reproduce the issue for my app, but focus filters are also broken for any third-party apps installed on my phone, so I guess it's not specific to how I've implemented my filter intent.
This used to work perfectly on iOS 17. I didn't change a single line of code, and it broke completely on the latest iOS 18 beta.
I've filed a bug report including a sysdiagnose (FB14715113).
To the developers out there, is this something you are also observing in your apps?
I have a SwiftUI List (on macOS) where I want to display a few TextFields in each row, and observe how the focus changes as an individual text field is selected or unselected. This seems to work fine if I just have the List display a pure TextField in each row, but if I embed the TextField inside a Button view, the focus change notifications stop working. I want the 'button' functionality so that the user can tap anywhere on the row and have the text field activated, instead of tapping exactly on the text field boundary.
Here is some code to demonstrate the problem. In the List if you have RowView uncommented out (1), then the focus change notifications work. If you comment that out and uncomment the RowViewWithButton (2), then the button functionality works, but I can't get focus change notifications anymore.
Here is the code to reproduce the issue. Interestingly, when I test this on iOS, it works fine in both cases. But I need the solution for macOS.
import SwiftUI
// test on macOS target
struct ContentView: View {
@State private var textFields = Array(repeating: "", count: 4)
@FocusState private var focusedField: Int?
var body: some View {
List(0..<4, id: \.self) { index in
// 1. works with focus notification changes
RowView(index: index, text: $textFields[index], focusedField: $focusedField)
// 2. button works, but no focus notifications on text field
//RowViewWithButton(index: index, text: $textFields[index], focusedField: $focusedField)
}
}
}
struct RowView: View {
let index: Int
@Binding var text: String
@FocusState.Binding var focusedField: Int?
var body: some View {
HStack {
Text("Row \(index + 1):")
TextField("", text: $text)
.multilineTextAlignment(.leading)
.focused($focusedField, equals: index)
}
.onChange(of: focusedField) { newFocus in
if newFocus == index {
print("TextField \(index) is in focus")
} else {
print("TextField \(index) lost focus")
}
}
.padding(.vertical, 4)
}
}
struct RowViewWithButton: View {
let index: Int
@Binding var text: String
@FocusState.Binding var focusedField: Int?
var body: some View {
Button (action: {
print("RowView - button selected at index \(index)")
focusedField = index
}) {
HStack {
Text("Row \(index + 1):")
TextField("", text: $text)
.multilineTextAlignment(.leading)
.focused($focusedField, equals: index)
}
.onChange(of: focusedField) { newFocus in
if newFocus == index {
print("TextField \(index) is in focus")
} else {
print("TextField \(index) lost focus")
}
}
.foregroundColor(.primary)
}
.buttonStyle(BorderlessButtonStyle())
.padding(.vertical, 4)
}
}
#Preview {
ContentView()
.frame(width: 320, height: 250)
}
Hey, I was wondering on how to react to the focus mode being turned off inside the FocusFilterIntent. I've successfully managed to call a specific action when the focus is being set, but I now want to deactivate/ react when the focus is being deactivated. How can I achieve something like this?