SWIFTUI LAYERING ISSUE: BACKGROUND ALWAYS APPEARS IN FRONT OF CONTENT

SWIFTUI LAYERING ISSUE: BACKGROUND ALWAYS APPEARS IN FRONT OF CONTENT

THE PROBLEM

I'm facing a frustrating issue in my SwiftUI macOS app where a background RoundedRectangle is consistently displaying in front of my content instead of behind it. This isn't an intermittent issue - it never works correctly. The colored background is always rendering on top of the text and icons, making the content completely unreadable.

Here's my current implementation:

private func sceneRow(for scene: Scene, index: Int) -> some View { ZStack(alignment: .leading) { // Hidden text to force view updates when state changes Text("$$noteStateTracker)") .frame(width: 0, height: 0) .opacity(0)

    // 1. Background rectangle explicitly at the bottom layer
    RoundedRectangle(cornerRadius: 6)
        .fill(sceneBackgroundColor(for: scene))
        .padding(.horizontal, 4)
    
    // 2. Content explicitly on top
    HStack {
        Image(systemName: "line.3.horizontal")
            .foregroundColor(.blue)
            .frame(width: 20)
        
        Text("$$index). $$truncateTitle(scene.title.isEmpty ? "Untitled Scene" : scene.title))")
            .foregroundColor(selectedScene?.id == scene.id ? .blue : .primary)
            .fontWeight(selectedScene?.id == scene.id ? .bold : .regular)
        
        Spacer()
        
        if scene.isComplete {
            Image(systemName: "checkmark.circle.fill")
                .foregroundColor(.green)
                .font(.system(size: 12))
                .padding(.trailing, 8)
        }
    }
    .padding(.vertical, 4)
    .padding(.leading, 30)
}
.contentShape(Rectangle())
.onTapGesture {
    selectedChapter = chapter
    selectedScene = scene
}
.onDrag {
    NSItemProvider(object: "$$scene.id.uuidString)|scene" as NSString)
}
.onDrop(of: ["public.text"], isTargeted: Binding(
    get: { hoveredSceneID == scene.id },
    set: { isTargeted in
        hoveredSceneID = isTargeted ? scene.id : nil
    }
)) { providers in
    handleSceneDrop(providers, scene, chapter)
}
.contextMenu {
    Button("Rename Scene") {
        sceneToRename = scene
        newSceneTitleForRename = scene.title
        newSceneDescriptionForRename = scene.description
        isRenamingScene = true
    }
    Button(role: .destructive) {
        confirmDeleteScene(scene, chapter)
    } label: {
        Label("Delete Scene", systemImage: "trash")
    }
}

}

Despite explicitly ordering elements in the ZStack with the background first (which should place it at the bottom of the stack), the RoundedRectangle always renders on top of the text and icons.

WHAT I'VE TRIED

I've attempted multiple approaches but nothing works:

  1. ZStack with explicit zIndex values

ZStack { RoundedRectangle(cornerRadius: 6) .fill(sceneBackgroundColor(for: scene)) .padding(.horizontal, 4) .zIndex(1)

HStack { /* content */ }
    .padding(.vertical, 4)
    .padding(.leading, 30)
    .zIndex(2)

}

No effect - background still appears on top.

  1. Using .background() modifier instead of ZStack

HStack { /* content */ } .padding(.vertical, 4) .padding(.leading, 30) .background( RoundedRectangle(cornerRadius: 6) .fill(sceneBackgroundColor(for: scene)) .padding(.horizontal, 4) )

Same issue - the background still renders in front of the content.

  1. Custom container view with GeometryReader

struct SceneRowContainer: View { var background: Background var content: Content

init(@ViewBuilder background: @escaping () -> Background, 
     @ViewBuilder content: @escaping () -> Content) {
    self.background = background()
    self.content = content()
}

var body: some View {
    GeometryReader { geometry in
        // Background rendered first
        background
            .frame(width: geometry.size.width, height: geometry.size.height)
            .position(x: geometry.size.width/2, y: geometry.size.height/2)
        
        // Content rendered second
        content
            .frame(width: geometry.size.width, height: geometry.size.height)
            .position(x: geometry.size.width/2, y: geometry.size.height/2)
    }
}

}

This changed the sizing of the components but didn't fix the layering issue.

  1. NSViewRepresentable approach

I tried implementing a custom NSViewRepresentable that manually manages the view hierarchy:

struct LayerOrderView: NSViewRepresentable { let background: () -> Background let content: () -> Content

func makeNSView(context: Context) -> NSView {
    let containerView = NSView()
    
    // Add background hosting view first (should be behind)
    let backgroundView = NSHostingView(rootView: background())
    containerView.addSubview(backgroundView)
    
    // Add content hosting view second (should be in front)
    let contentView = NSHostingView(rootView: content())
    containerView.addSubview(contentView)
    
    // Setup constraints...
    
    return containerView
}

func updateNSView(_ nsView: NSView, context: Context) {
    // Update views...
}

}

Even this direct AppKit approach didn't work correctly.

  1. Using .drawingGroup()

ZStack { // Background RoundedRectangle(cornerRadius: 6) .fill(sceneBackgroundColor(for: scene)) .padding(.horizontal, 4)

// Content
HStack { /* content */ }
    .padding(.vertical, 4)
    .padding(.leading, 30)

} .drawingGroup(opaque: false)

Still no success - the background remains in front.

PROJECT CONTEXT

  • macOS app using SwiftUI
  • Scene contents need to be displayed on top of colored backgrounds
  • The view uses state tracking with a noteStateTracker UUID that updates when certain changes occur
  • App needs to maintain gesture recognition for taps, drag and drop, and context menus
  • The issue is completely reproducible 100% of the time - the background is always in front

WHAT I WANT TO ACHIEVE

I need a reliable solution to ensure that the background color (RoundedRectangle) renders behind the HStack content. The current behavior makes the text content completely unreadable since it's hidden behind the colored background.

Has anyone found a workable solution for this seemingly basic layering problem in SwiftUI on macOS?

Thank you for any help, Benjamin

Please fix the code-box

code block here

so that all the braces including the ZStack are in the code block. I can't quite tell if the problem is unbalanced {}s or not. (I suspect not, but it'ts hard to tell in your posting).

ignore this, I cant delete a post.

    ZStack(alignment: .leading) {
        // Hidden text to force view updates when state changes
        Text("\(noteStateTracker)")
            .frame(width: 0, height: 0)
            .opacity(0)
            
        // 1. Background rectangle explicitly at the bottom layer
        RoundedRectangle(cornerRadius: 6)
            .fill(sceneBackgroundColor(for: scene))
            .padding(.horizontal, 4)
        
        // 2. Content explicitly on top
        HStack {
            Image(systemName: "line.3.horizontal")
                .foregroundColor(.blue)
                .frame(width: 20)
            
            Text("\(index). \(truncateTitle(scene.title.isEmpty ? "Untitled Scene" : scene.title))")
                .foregroundColor(selectedScene?.id == scene.id ? .blue : .primary)
                .fontWeight(selectedScene?.id == scene.id ? .bold : .regular)
            
            Spacer()
            
            if scene.isComplete {
                Image(systemName: "checkmark.circle.fill")
                    .foregroundColor(.green)
                    .font(.system(size: 12))
                    .padding(.trailing, 8)
            }
        }
        .padding(.vertical, 4)
        .padding(.leading, 30)
    }
    .contentShape(Rectangle())
    .onTapGesture {
        selectedChapter = chapter
        selectedScene = scene
    }
    .onDrag {
        NSItemProvider(object: "\(scene.id.uuidString)|scene" as NSString)
    }
    .onDrop(of: ["public.text"], isTargeted: Binding<Bool>(
        get: { hoveredSceneID == scene.id },
        set: { isTargeted in
            hoveredSceneID = isTargeted ? scene.id : nil
        }
    )) { providers in
        handleSceneDrop(providers, scene, chapter)
    }
    .contextMenu {
        Button("Rename Scene") {
            sceneToRename = scene
            newSceneTitleForRename = scene.title
            newSceneDescriptionForRename = scene.description
            isRenamingScene = true
        }
        Button(role: .destructive) {
            confirmDeleteScene(scene, chapter)
        } label: {
            Label("Delete Scene", systemImage: "trash")
        }
    }
}
SWIFTUI LAYERING ISSUE: BACKGROUND ALWAYS APPEARS IN FRONT OF CONTENT
 
 
Q