Question
I'm trying to replicate the iOS 26 Photos app behavior where a segmented control appears in the tab bar area when scrolling. Specifically, when the tab bar minimizes to inline mode, I want to show a
Picker with .segmented style between the minimized tab button and search button.
Expected Behavior (iOS 26 Photos App)
When scrolling up in the Photos app:
- Tab bar minimizes to inline mode
- A segmented control (Years/Months/All) appears in the center
- Layout:
[Tab Button] [Segmented Control] [Search Button]
Current Implementation
I'm using tabViewBottomAccessory with tabBarMinimizeBehavior:
struct PhotosMainView: View {
@Environment(ModelData.self) private var modelData
@State private var searchText: String = ""
var body: some View {
@Bindable var modelData = modelData
TabView {
Tab("Library", systemImage: "photo.on.rectangle") {
NavigationStack {
PhotosGridView()
.navigationTitle("Library")
}
}
Tab("Albums", systemImage: "square.grid.2x2") {
NavigationStack {
AlbumsGridView()
.navigationTitle("Albums")
}
}
Tab("Search", systemImage: "magnifyingglass", role: .search) {
NavigationStack {
PhotosGridView()
.navigationTitle("Search")
.searchable(text: $searchText)
}
}
}
.tabBarMinimizeBehavior(.onScrollUp)
.tabViewBottomAccessory {
TimelineAccessoryView()
.environment(modelData)
}
}
}
struct TimelineAccessoryView: View {
@Environment(ModelData.self) private var modelData
@Environment(\.tabViewBottomAccessoryPlacement) var placement
var body: some View {
Group {
if let placement = placement {
switch placement {
case .inline:
inlineView
case .expanded:
expandedView
}
}
}
}
@ViewBuilder
private var inlineView: some View {
@Bindable var modelData = modelData
Picker("View", selection: $modelData.timelineFilter) {
Text("Years").tag(TimelineFilter.year)
Text("Months").tag(TimelineFilter.month)
Text("All").tag(TimelineFilter.all)
}
.pickerStyle(.segmented)
.frame(maxWidth: 200)
}
@ViewBuilder
private var expandedView: some View {
Text("Expanded state")
}
}
Issues Encountered
1. Console logs show placement changes correctly:
placement: nil → expanded → inline
2. However, the segmented control doesn't appear visually in inline mode
- The accessory view seems to render, but nothing is visible on screen
- Only a small empty space appears where the control should be
3. AttributeGraph cycle warnings appear:
=== AttributeGraph: cycle detected through attribute 27160 ===
=== AttributeGraph: cycle detected through attribute 26304 ===
What I've Tried
1. ✅ Separating inline/expanded views into @ViewBuilder properties to avoid cycles
2. ✅ Using .onAppear and .onChange for debugging instead of direct prints in body
3. ✅ Confirming placement environment value changes correctly
4. ❌ The segmented control still doesn't display in inline mode
Questions
1. Is tabViewBottomAccessory the correct API to achieve this Photos app effect?
2. How should content be structured in .inline placement to display properly between tab button and search?
3. Are there additional modifiers or constraints needed for inline accessories?
4. Is there documentation or sample code showing this pattern?
Environment
- Xcode 17.0
- iOS 26.0
- iPhone 16 Simulator
- SwiftUI
Any guidance on the correct approach to implement this would be greatly appreciated. Thank you!
I'm unable to reproduce the issue, as reported, in my own Xcode project. Because the provided example above will not build successfully due to missing types, I replaced your implementation with the following:
import SwiftUI
struct PhotosMainView: View {
@State private var searchText: String = ""
var body: some View {
TabView {
Tab("Library", systemImage: "photo.on.rectangle") {
NavigationStack {
scrollView(colors: [.red, .orange, .yellow])
.navigationTitle("Library")
}
}
Tab("Albums", systemImage: "square.grid.2x2") {
NavigationStack {
scrollView(colors: [.yellow, .green, .teal])
.navigationTitle("Albums")
}
}
Tab("Search", systemImage: "magnifyingglass", role: .search) {
NavigationStack {
scrollView(colors: [.blue, .purple, .pink])
.navigationTitle("Search")
.searchable(text: $searchText)
}
}
}
.tabBarMinimizeBehavior(.onScrollUp)
.tabViewStyle(.sidebarAdaptable)
.tabViewBottomAccessory {
TimelineAccessoryView()
}
}
// Trivial helper view to visualize scrolling.
@ViewBuilder func scrollView(colors: [Color]) -> some View {
ScrollView{
Rectangle()
.fill(Gradient(colors: colors))
.frame(height: 2000)
}
}
}
struct TimelineAccessoryView: View {
@SceneStorage("selectedTimeline") var selection: Int = 0
@Environment(\.tabViewBottomAccessoryPlacement) var placement
var body: some View {
Group {
if let placement = placement {
switch placement {
case .inline:
inlineView
case .expanded:
expandedView
@unknown default:
fatalError()
}
}
}
}
@ViewBuilder
private var inlineView: some View {
Picker("View", selection: $selection) {
Text("Years").tag(0)
Text("Months").tag(1)
Text("All").tag(2)
}
.pickerStyle(.segmented)
.frame(maxWidth: 200)
}
@ViewBuilder
private var expandedView: some View {
Text("Expanded state")
}
}
#Preview {
PhotosMainView()
}
With the above changes, I was able to see the inline segmented control on an iPhone 16 as well as the iPhone 16 Simulator. If these changes resolve the issue in your project, then I'd suggest refocusing your debugging efforts to the contents of the tabs—perhaps binary searching by abstracting or simplifying child views until you narrow down the API inhibiting the display of the inline segmented control.
However, if the code above does not resolve your issue, please submit a report via Feedback Assistant, including a demo Xcode project, tested iOS and Xcode versions (including build numbers), and reproduction steps. Once submitted, please reply here with the Feedback ID so I can resume my investigation.
Cheers,
Paris X Pinkney | WWDR | DTS Engineer