Poor ScrollView performance

Hello,

I have a scroll view that when it appears I get a constant stream of warnings from the console:

CGBitmapContextInfoCreate: CGColorSpace which uses extended range requires floating point or CIF10 bitmap context

This happens whether or not I'm actively scrolling, so maybe it's not a scroll view issue at all? The reason I initially thought it was a scrollView issue was because when I scrolled the scrolling was no longer smooth, it was pretty choppy.

Also, I only get these warnings when I run my code on a real device - I do not get these on any simulators. Could it be the mesh gradient causing an issue?

I'm not sure what's causing the issue so I apologize in advance for what may be irrelevant code.

struct ModernStoryPicker: View {
    @Environment(CategoryPickerViewModel.self) var categoryPickerViewModel
    @EnvironmentObject var navigationPath: GrowNavigationState
    @State private var hasPreselectedStory: Bool = false
    @State private var storyGenerationType: StoryGenerationType = .arabicCompanion

    var userInstructions: String {
        if categoryPickerViewModel.userInputCategory.isEmpty {
            "Select a category"
        } else if categoryPickerViewModel.userInputSubCategory.isEmpty {
            "Select a subcategory"
        } else {
            "Select a story"
        }
    }

    var body: some View {
        ZStack {
            MeshGradient(width: 3, height: 3, points: [
                [0.0, 0.0], [0.5, 0.0], [1.0, 0.0],
                [0.0, 0.5], [0.9, 0.3], [1.0, 0.5],
                [0.0, 1.0], [0.5, 1.0], [1.0, 1.0]

            ], colors: [
                .orange, .indigo, .orange,
                .blue, .blue, .cyan,
                .green, .indigo, .pink
            ])
            .ignoresSafeArea()
            VStack {
                Picker("", selection: $storyGenerationType) {
                    ForEach(StoryGenerationType.allCases) { type in
                        Text(type.rawValue).tag(type)
                    }
                }.pickerStyle(.segmented)
                Text(userInstructions)
                    .textScale(.secondary)
                automatedOrUserStories()
                Spacer()
            }.padding()
        }
        .onAppear {...}
    }

    @ViewBuilder func automatedOrUserStories() -> some View {
        switch storyGenerationType {
        case .userGenerated:
            VStack {
                Spacer()
                Text("Coming soon!")
            }
        case .arabicCompanion:
            VStack {
                // TODO: Unnecessary passing of data, only the 2nd view really needs all these props
                CategoryPickerView(
                    categories: categoryPickerViewModel.mainCategories(), isSubCategory: false,
                    selectionColor: .blue
                )
                CategoryPickerView(
                    categories: categoryPickerViewModel.subCategories(), isSubCategory: true,
                    selectionColor: .purple
                )
                ScrollView(.horizontal) {
                    HStack {
                        if categoryPickerViewModel.booksForCategories.isEmpty {
                            Text("More books coming soon!")
                        }
                        ForEach(categoryPickerViewModel.booksForCategories) { bookCover in
                            Button(action: {
                                // navigates to BookDetailView.swift
                                navigationPath.path.append(bookCover)
                            }) {
                                ModernStoryCardView(
                                    loadedImage: categoryPickerViewModel.imageForBook(bookCover),
                                    bookCover: bookCover
                                )
                                .scrollTransition(axis: .horizontal) { content, phase in
                                    content
                                        .scaleEffect(phase.isIdentity ? 1 : 0.5)
                                        .opacity(phase.isIdentity ? 1 : 0.8)
                                }
                            }.buttonStyle(.plain)
                        }
                    }.scrollTargetLayout()
                }
                .contentMargins(80, for: .scrollContent)
                .scrollTargetBehavior(.viewAligned)
            }.padding()
        }
    }
}

struct CategoryPickerView: View {
    @Environment(CategoryPickerViewModel.self) var viewModel
    let categories: [String]
    let isSubCategory: Bool
    let selectionColor: Color
    var body: some View {
        ScrollView(.horizontal) {
            HStack {
                ForEach(categories, id: \.self) { category in
                    Button(action: {
                        withAnimation {
                            selectCategory(category)
                        }
                    }) {
                        TextRoundedRectangleView(text: category, color: effectiveColor(for: category))
                            .tag(category)
                    }.buttonStyle(.plain)
                }
            }
        }.scrollIndicators(.hidden)
    }
    private func selectCategory(_ string: String) {
        if !isSubCategory {
            viewModel.userInputCategory = string
        } else {
            viewModel.userInputSubCategory = string
        }
    }

    private func effectiveColor(for category: String) -> Color {
        let chosenCategory = isSubCategory ? viewModel.userInputSubCategory : viewModel.userInputCategory
        if category == chosenCategory {
            return selectionColor
        }
        return .white
    }
}

Those errors show up from trying to use CoreGraphics bitmaps with integer values but in a colorspace where we really need float. System Frameworks may use CGBitmapContexts but they would likely handle this correctly. Do you make use of CGBitmapContext anywhere? To my eyes, likely suspects could be either ModernStoryCardView or TextRoundedRectangleView. Do you have the code for those?

@patrick_metcalfe yes

struct ModernStoryCardView: View {
    var loadedImage: Image?
    let bookCover: BookCover
    var body: some View {
        ZStack {
            VStack {
                ZStack(alignment: .topTrailing) {
                    ImageOrPlaceholder()
                    // RoundedRectangle(cornerRadius: 8)

                    HStack {
                        Image(systemName: "star.fill")
                            .foregroundStyle(.yellow)
                            .imageScale(.small)
                        Text(bookCover.rating.description)
                            .foregroundStyle(.yellow)
                            .textScale(.secondary)
                    }
                    .padding(.vertical, 5)
                    .padding(.horizontal, 5)
                    .background(.ultraThinMaterial.opacity(0.3))
                    .clipShape(RoundedRectangle(cornerRadius: 4))
                    .padding(10)
                }
                Text(bookCover.title)
                    .font(.headline)
                Text(bookCover.authorName)
                    .font(.subheadline)
                    .foregroundStyle(.primary)
            }
        }
        .containerRelativeFrame(.horizontal, count: 1, spacing: 10)
        .frame(height: 300)
    }
    @ViewBuilder private func ImageOrPlaceholder() -> some View {
        if let loadedImage {
            loadedImage
                .resizable()
                .aspectRatio(9/16, contentMode: .fill)
                .cornerRadius(8)
        } else {
            Image(systemName: "photo")
                .resizable()
                .aspectRatio(9/16, contentMode: .fill)
                .foregroundStyle(.gray)
        }
    }
}

struct TextRoundedRectangleView: View {
    let text: String
    let color: Color
    var body: some View {
        Text(text)
            .foregroundStyle(.primary)
            .padding(.horizontal, 10)
            .background(color.opacity(0.6))
            .containerShape(RoundedRectangle(cornerRadius: 8))
            .lineLimit(1)
    }
}

Thank you. Nothing there strikes me as unusual or even something that might hit CGBitmapContextCreate. What I would recommend is to set a breakpoint in Xcode for that symbol.

Set the breakpoint for CGBitmapContextInfoCreate. The caller is likely to be CGBitmapContextCreate, and then may have framework symbols in between which you may or may not be able to symbolicate.

If you cannot recognize the symbols or the provenance of the call then try ablating views from your hierarchy (that is, commenting them out) until the console errors stop. This will at least give you a clearer picture of what might be causing this to be hit. Try and reduce that to a simpler reproduction case and then file after feedback with that to SwiftUI.

I did set the symbolic breakpoint and unfortunately nothing is symbolicated.

But here is one interesting thing. The warnings goes away (and performance improves) when I remove the .ignoresSafeArea that is modifying the MeshGradient. I'll keep messing around with it.

Edit to add more context:

So like I said removing .ignoresSafeArea works, but obviously the view will look bad. The warnings also go away when I remove the MeshGradient.

Poor ScrollView performance
 
 
Q