Home > Software design >  How to apply SwiftUI opacity to the parent View only?
How to apply SwiftUI opacity to the parent View only?

Time:05-27

Applying opacity to the View in SwiftUI passing it down through the hierarchy and looks like it works kinda Environment value. It causes the behaviour when each subview is being affected by opacity independently. No matter if we put the yellow circle with the overlay, background, or inside ZStack.

enter image description here

How can we make opacity affecting to the parent view only, like it behaves in UIKit, so the whole view with all its content will became translucent as a single solid object?

struct OpacityTest: View {
    var body: some View {
        HStack(spacing: 30) {
            buttonContent
                .opacity(0.5)

            Button(action: {}) {
                buttonContent
            }
        }
    }
    
    var buttonContent: some View {
        Image(systemName: "calendar.circle")
            .resizable()
            .frame(width: 65, height: 65)
            .overlay(
                Circle()
                    .fill(Color.yellow)
                    .frame(width: 40, height: 40)
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
            )
    }
}

struct OpacityTest_Preview: PreviewProvider {
    static var previews: some View {
        OpacityTest()
    }
}

Update: As ChrisR answered, .drawingGroup() does the job and partially solves the issue, but until everything is inside the parent view bounds. Once we add offset to the view inside overlay, it's being cropped. Any chance to avoid that?

.overlay(
    Circle()
        .fill(Color.yellow)
        .frame(width: 40, height: 40)
        .offset(x: 10, y: -10) //This modifier brakes the drawingGroup
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
    )

enter image description here

CodePudding user response:

Add..drawingGroup(). This will apply all further modifiers to the whole object/view:

    var buttonContent: some View {
        Image(systemName: "calendar.circle")
            .resizable()
            .frame(width: 65, height: 65)
            .overlay(
                Circle()
                    .fill(Color.yellow)
                    .frame(width: 40, height: 40)
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
            )
        .drawingGroup() // here
    }

EDIT as to OPs Update:

.offset does not change the size/bounds of the view but just shifts the rendering, which leads to unwanted clipping. You could go with geometryReader but in the example with fixed sizes it should be enough to enlarge the parent frame manually to avoid the clipping:

    var buttonContent: some View {
        ZStack(alignment: .topTrailing) {
            Image(systemName: "calendar.circle")
                .resizable()
                .frame(width: 65, height: 65)
            Circle()
                .fill(Color.yellow)
                .frame(width: 40, height: 40)
                .offset(x: 10, y: -10)
        }
        .frame(width: 95, height: 95) // here
        .drawingGroup()
    }

CodePudding user response:

Adding .compositingGroup() does the job. It makes external modifiers work only on the top level and not being passed through hierarchy. Also does not crop any subviews with offset and keeps original frame for layout.

var buttonContent: some View {
    Image(systemName: "calendar.circle")
        .resizable()
        .frame(width: 65, height: 65)
        .overlay(
            Circle()
                .fill(Color.yellow)
                .frame(width: 40, height: 40)
                .offset(x: 10, y: -10)
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
        )
        .compositingGroup() //here
}

CodePudding user response:

If we apply any modifier to parent View it also effect on child Views as well.

Remove .opacity(0.5) from buttonContent and add it before .overlay modifier

  • Related