1

I'm trying to animate opacity on appear/disappear of Text view with a simple linear gradient.

Here's the "minimum" code I have right now:

import SwiftUI

struct ContentView: View {
    @State var shown: Bool = true

    var body: some View {
        VStack {
            ZStack {
                if shown {
                    TextView()
                }
            }
            .frame(height: 100)

            Button("Show Toggle") {
                shown.toggle()
            }
        }
    }
}

struct TextView: View {
    var body: some View {
        text
            .overlay(gradient)
            .mask(text)
            .transition(transition)
    }
    
    var gradient: LinearGradient {
        LinearGradient(
            colors: [Color.white, Color.blue],
            startPoint: .leading, endPoint: .trailing
        )
    }
    
    var transition: AnyTransition {
        .asymmetric(
            insertion: .opacity.animation(.linear(duration: 0.500)),
            removal: .opacity.animation(.linear(duration: 0.500))
        )
    }

    var text: some View {
        Text("Hello World")
            .fontWeight(.bold)
            .font(.largeTitle)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Notice: I'm using the view.overlay(gradient).mask(view) pattern in order for the gradient view to not be greedy.

As a result, the animation looks like this:

Resulting animation

Notice how the view doesn't "just" fade out -- it first turns black and then fades out.

I can fix opacity issues by just doing gradient.mask(text) instead of the other pattern, but then I run into other issues with the gradient view being greedy

2 Answers 2

3

You are drawing a black text, then overlay the gradient, then clip it with the text. Just omit the first black text, e.g.by setting its opacity to 0.

        text.opacity(0)
            .overlay(gradient)
            .mask(text)
            .transition(transition)

or use .drawingGroup which renders the whole view offscreen before displaying:

        text
            .overlay(gradient)
            .mask(text)
            .drawingGroup() // here
            .transition(transition)
Sign up to request clarification or add additional context in comments.

Comments

0

Thought it might be helpful to note that .drawingGroup() uses Metal, which may not be necessary for your purposes in this example.

You can achieve the same effect by using .compositingGroup(), without using Metal.

        text
            .overlay(gradient)
            .mask(text)
            .compositingGroup() // here
            .transition(transition)

Per Apple's documentation:

A compositing group makes compositing effects in this view’s ancestor views, such as opacity and the blend mode, take effect before this view is rendered. Use compositingGroup() to apply effects to a parent view before applying effects to this view.

2 Comments

If you recommend using .compositingGroup(), why do you use .drawingGroup()in your example?
Ah, a simple mistake. Fixed. Thanks for pointing it out.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.