Fix text in accessory view

Do you guys know how to fix the render of the text in the accessory view ? If I force the color of text to be .black it work but it will break dark mode, but forcing it .black : .white on color scheme changes makes white to still adapt to what is behind it I have noticed that Apple Music doesn’t have that artifact and it seems to break when images are behind the accessory view

// MARK: - Next Routine Accessory
@available(iOS 26.0, *)
struct NetxRoutinesAccessory: View {
    @ObservedObject private var viewModel = RoutineProgressViewModel.shared
    @EnvironmentObject var colorSchemeManager: ColorSchemeManager
    @EnvironmentObject var routineStore: RoutineStore
    @EnvironmentObject var freemiumKit: FreemiumKit
    @ObservedObject var petsStore = PetsStore.shared
    @Environment(\.colorScheme) private var colorScheme
    
    // Tab accessory placement environment
    @Environment(\.tabViewBottomAccessoryPlacement) private var accessoryPlacement
    
    // Navigation callback
    var onTap: (() -> Void)?
    
    @State private var isButtonPressed = false
    
    /// Explicit black for light mode, white for dark mode
    private var textColor: Color {
        colorScheme == .dark ? .trueWhite : .trueBlack
    }
    
    /// Returns true when the accessory is in inline/minimized mode
    private var isInline: Bool {
        accessoryPlacement == .inline
    }
    
    var body: some View {
        accessoryContent()
            .onTapGesture {
                onTap?()
            }
    }
    
    private func accessoryContent() -> some View {
        HStack(spacing: 12) {
            
            // Content with smooth transitions
            VStack(alignment: .leading, spacing: 2) {
                if viewModel.totalTasks == 0 {
                    Text(NSLocalizedString("Set up routines", comment: "Routines empty state"))
                        .font(.subheadline.weight(.medium))
                        .foregroundColor(textColor)
                } else if let next = viewModel.nextRoutineTask() {
                    HStack(spacing: 4) {
                        Text(NSLocalizedString("Next", comment: "Next routine prefix"))
                            .font(.caption)
                            .foregroundColor(textColor)
                        Text("•")
                            .font(.caption)
                            .foregroundColor(textColor)
                        Text(next.routine.name)
                            .font(.subheadline.weight(.medium))
                            .foregroundColor(textColor)
                            .lineLimit(1)
                    }
                    .id("routine-\(next.routine.id)-\(next.time)")
                    .transition(.opacity.combined(with: .move(edge: .leading)))
                    
                    HStack(spacing: 4) {
                        Text(viewModel.petNames(for: next.routine.petIDs))
                            .font(.caption)
                            .foregroundColor(textColor)
                        Text("•")
                            .font(.caption)
                            .foregroundColor(textColor)
                        Text(Routine.displayTimeFormatter.string(from: next.time))
                            .font(.caption.weight(.medium))
                            .foregroundColor(colorSchemeManager.accentColor ?? .blue)
                    }
                    .id("time-\(next.routine.id)-\(next.time)")
                    .transition(.opacity.combined(with: .move(edge: .leading)))
                } else {
                    // All tasks completed
                    Text(NSLocalizedString("All done for today!", comment: "All routines completed"))
                        .font(.subheadline.weight(.medium))
                        .foregroundColor(textColor)
                        .transition(.opacity.combined(with: .scale))
                    
                    Text("\(viewModel.completedTasks)/\(viewModel.totalTasks) " + NSLocalizedString("tasks", comment: "Tasks count suffix"))
                        .font(.caption)
                        .foregroundColor(textColor)
                }
            }
            .animation(colorSchemeManager.reduceMotion ? nil : .snappy(duration: 0.3), value: viewModel.completedTasks)
            .animation(colorSchemeManager.reduceMotion ? nil : .snappy(duration: 0.3), value: viewModel.progress)
        }
        .padding()
        .contentShape(.rect)
        .animation(colorSchemeManager.reduceMotion ? nil : .snappy(duration: 0.35), value: viewModel.completedTasks)
    }
}

FB21365706

It looks like the tabViewBottomAccessory view doesn’t adapt properly when the background isn’t white. Interestingly, the tab bar itself adapts perfectly.

struct ContentView: View {
    enum TabValue: String {
        case first, second
    }
    
    @State var selectedTab: TabValue = .first

    var body: some View {
        TabView(selection: $selectedTab) {
            Tab("first", systemImage: "1.circle", value: .first) {
                ScrollView(.vertical) {
                    Spacer(minLength: 800)

                    LinearGradient(
                        colors: [.yellow, .black],
                        startPoint: .top,
                        endPoint: .bottom
                    )
                    .frame(height: 800)
                }
            }
            
            Tab("second", systemImage: "2.circle", value: .second) { }
        }
        .tabViewBottomAccessory {
            HStack {
                Text("Title")
                Spacer()
                Image(systemName: "play.fill")
            }
            .padding(.horizontal)
        }
    }
}

Any update?

Still not fixed on iOS 26.4 Beta 1.

FB21365706

Hello @danielcr12, @dsadasdasda

Thank you for your question and providing examples. This might help explain what's going on and further steps you can take.

By default, ScrollView has a .soft scroll edge effect at the bottom and top of its view. If I run @dsadasdasda example in Light Mode, you can see this effect results in a white gradient at the bottom of the view.

If I switch this to a hard transition by applying a .scrollEdgeEffectStyle(.hard, for: .bottom) to the ScrollView, we get a more fair comparison of the two:

Which looks pretty similar to me!

For more info on scroll edge effects, visit .scrollEdgeEffectStyle

Let me know if this helps,

 Travis Trotto - DTS Engineer

@dsadasdasda @MarkTheShark

The current status of your Feedback can be viewed in Feedback Assistant under Resolution. You can track if the report is still being investigated, has a potential identified fix, or has been resolved in another way.

For more details on Feedback Status, please see “Understanding the Status of Your Feedback” linked here: https://developer.apple.com/bug-reporting/status

Thank you!

 Travis Trotto - DTS Engineer

Thank you for taking a look.

While you could argue that hard scroll edge fixes this issue, it actually looks terrible and defeats the whole purpose of Liquid Glass. Here's how it would look like in real app and not trivial solid color background example.

soft (including illegible text bug):

hard:

I attempted to reproduce the bug in UIKit, but it works correctly there. Notice how the Tab Bar and the accessory view change color simultaneously.

final class ViewController: UITabBarController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let firstVC = FirstTabVC()
        firstVC.tabBarItem = UITabBarItem(
            title: "first",
            image: UIImage(systemName: "1.circle"),
            tag: 0
        )
        
        let secondVC = SecondTabVC()
        secondVC.tabBarItem = UITabBarItem(
            title: "second",
            image: UIImage(systemName: "2.circle"),
            tag: 1
        )
        
        viewControllers = [firstVC, secondVC]
        
        setupBottomAccessory()
    }
    
    func setupBottomAccessory() {
        let titleLabel = UILabel()
        titleLabel.text = "Title"
        
        let iconView = UIImageView(image: UIImage(systemName: "play.fill"))
        iconView.preferredSymbolConfiguration = .init(pointSize: 20, weight: .semibold)
        iconView.tintColor = .label
        iconView.contentMode = .scaleAspectFit
        
        let stackView = UIStackView(arrangedSubviews: [titleLabel, iconView])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        let accessoryView = UIView()
        accessoryView.addSubview(stackView)
        
        NSLayoutConstraint.activate([
            accessoryView.heightAnchor.constraint(equalToConstant: 48),
            stackView.topAnchor.constraint(equalTo: accessoryView.topAnchor),
            stackView.leadingAnchor.constraint(equalTo: accessoryView.leadingAnchor, constant: 16),
            stackView.trailingAnchor.constraint(equalTo: accessoryView.trailingAnchor, constant: -16),
            stackView.bottomAnchor.constraint(equalTo: accessoryView.bottomAnchor),
        ])
        
        bottomAccessory = UITabAccessory(contentView: accessoryView)
    }
}

private final class FirstTabVC: UIViewController {
    private let scrollView = UIScrollView()
    private let contentView = UIView()
    private let topSpacer = UIView()
    private let gradientView = GradientView()

    override func viewDidLoad() {
        super.viewDidLoad()

        scrollView.translatesAutoresizingMaskIntoConstraints = false
        contentView.translatesAutoresizingMaskIntoConstraints = false
        topSpacer.translatesAutoresizingMaskIntoConstraints = false
        gradientView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        contentView.addSubview(topSpacer)
        contentView.addSubview(gradientView)

        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),

            topSpacer.topAnchor.constraint(equalTo: contentView.topAnchor),
            topSpacer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            topSpacer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            topSpacer.heightAnchor.constraint(equalToConstant: 800),

            gradientView.topAnchor.constraint(equalTo: topSpacer.bottomAnchor),
            gradientView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            gradientView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            gradientView.heightAnchor.constraint(equalToConstant: 800),
            gradientView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
    }
}

private final class SecondTabVC: UIViewController { }

private final class GradientView: UIView {
    override class var layerClass: AnyClass { CAGradientLayer.self }

    override init(frame: CGRect) {
        super.init(frame: frame)
        let layer = layer as! CAGradientLayer
        layer.colors = [UIColor.systemYellow.cgColor, UIColor.black.cgColor]
        layer.startPoint = CGPoint(x: 0.5, y: 0.0)
        layer.endPoint = CGPoint(x: 0.5, y: 1.0)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Hello @danielcr12

In this situation, the color of the text depends on the color behind the text.

You have a gradient that's .black on the bottom.

This is expected behavior.

Change LinearGradient(...).frame(height: 800) to Color(.yellow).frame(height: 800)

And you will see that they update at the same time, and that it was the black gradient causing the timing change.

 Travis Trotto - DTS Engineer

On second thought, considering that this works differently in UIKit then SwiftUI, I think its best we file a bug report and get to the bottom of this.

Please file a bug report here and reply with the feedback number once complete, I'll make sure it winds up with the correct team.

Thank you,  Travis Trotto - DTS Engineer

FB21365706

All reproducible code samples and screen recordings showing the issue are attached.

Fix text in accessory view
 
 
Q