How Do I Fade the Top or Bottom Edge of a SF Symbol in SwiftUI?
Asked Answered
F

2

6

I'm trying to recreate a visual, that consist of 3 SF Symbols. The image in the middle is 100% solid no fade. I would like to fade top edge of the top most image and fade the bottom edge of the bottom most image.

I am using SF Symbols.

I would like to reproduce the following:

(fade top of image)

IMAGE

IMAGE

IMAGE

(fade bottom of image)

How would I accomplish this?

Formaldehyde answered 24/1, 2020 at 21:33 Comment(1)
Hard to answer - your question isn't vague at all IMHO but it's... well, first, can you be sure it can be done? I'm not even talking about SwiftUI, I'm talking UIKit. I think I have a mental picture of what you want, but could you supply an actual image? And yes, by that, I';m asking if you've seen this done with SFSymbols. Finally, if it can be done, have you considered what (a) SFSymbols are - vectors, (b) what CoreGraphics may bring to the table, (c) what CoreImage might do, but mostly (d) how constrained SwiftUI is currently?Kayleen
U
12

Here is my alternate (without UIKit usage)

enter image description here

struct TestSFSymbolsFade: View {
    var body: some View {
        VStack {
            Image(systemName: "ant.fill").resizable()
            Image(systemName: "ant.fill").resizable()
            Image(systemName: "ant.fill").resizable()
        }
        .mask(LinearGradient(gradient: Gradient(stops: [
            .init(color: .clear, location: 0),
            .init(color: .black, location: 0.25),
            .init(color: .black, location: 0.75),
            .init(color: .clear, location: 1)
        ]), startPoint: .top, endPoint: .bottom))
        .frame(width: 40, height: 160) // < just for demo, can be any or absent
    }
}
Uniformity answered 25/1, 2020 at 5:54 Comment(0)
M
7

First, keep in mind that there are usage rules around SF Symbols. It may or may not be allowed to modify them in this way. In particular there is a list of "As-Is" symbols that cannot be used for arbitrary purposes. But that doesn't change much about the answer.

The second thing to note is that SF Symbols are a strange beast between text and images. They often act like text, and they often draw outside their frames. That creates problems. You can fix that by forcing them to be real images as discussed in the linked answer.

let config = UIImage.SymbolConfiguration(scale: .large)
let image = Image(uiImage: UIImage(systemName:"star.fill", withConfiguration: config)!)

In the end, as long as you can get an Image that has the correct frame, the rest of this will work. It doesn't matter that it's a SF Symbol.

Asperi's answer explains how to fix this by using resizable.

let image = Image(systemName: "star.fill").resizable().aspectRatio(contentMode: .fit)

(I've added aspectRatio so it doesn't distort.)

First, define your Gradient. This is how the fade will work, and you can configure it with a set of colors or individual color stops. The only thing that matters here is the opacity.

let fade =  Gradient(colors: [Color.clear, Color.black])

(See Asperi's answer for an example using stops, which give you more control over the fade.)

With that, you can apply this gradient as a mask:

let fade =  Gradient(colors: [Color.clear, Color.black])
let image = Image(systemName: "star.fill")
    .resizable().aspectRatio(contentMode: .fit).frame(height: 48)

struct ContentView: View {
    var body: some View {
        VStack {
            image
                .mask(LinearGradient(gradient: fade, startPoint: .top, endPoint: .bottom))
            image
            image
                .mask(LinearGradient(gradient: fade, startPoint: .bottom, endPoint: .top))
        }
    }
}

This raises issues about sizing. This approach is a flexible stack. It will grow to fill whatever container it's put in. You may want to constrain it a couple of ways.

First, you can decide the height of the entire stack by putting a frame on it:

    VStack {
        image
            .mask(LinearGradient(gradient: fade, startPoint: .top, endPoint: .bottom))
        image
        image
            .mask(LinearGradient(gradient: fade, startPoint: .bottom, endPoint: .top))
    }.frame(height: 128)
     ^^^^^^^^^^^^^^^^^^^

Or you could fix the size of each image instead:

let image = Image(systemName: "star.fill")
    .resizable().aspectRatio(contentMode: .fit).frame(width: 48)
                                               ^^^^^^^^^^^^^^^^^

Three symbols with fading at top and bottom

Morganstein answered 24/1, 2020 at 22:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.