web-dev-qa-db-fra.com

Masque Rectangle avec un texte à Swiftui

Je veux atteindre suivi avec Swiftui:

masked rectangle

C'est ce que j'ai essayé:

Text("test").mask(Rectangle().frame(width: 200, height: 100).foregroundColor(.white))

Aussi l'inverse:

Rectangle().frame(width: 200, height: 100).foregroundColor(.white).mask(Text("test"))

Ces deux échantillons m'ont donné le résultat inverse de ce que je voulais. Ce qui signifie que seul le texte montrait en blanc avec le rectangle étant "masqué".

J'ai aussi pensé à l'alternative où je combine simplement Text et Rectangle dans un ZStack. Le rectangle ayant la couleur de premier plan et le texte de la couleur de fond. Cela entraînerait le même effet. Cependant, je ne veux pas faire cela car cela semble être un hack pour moi. Par exemple, si je veux ajouter un gradient ou une image à l'arrière-plan, cette méthode ne fonctionnerait pas très bien.

Y a-t-il un bon moyen sur la façon de faire cela à Swiftui? Cela ne me dérangerait pas si c'est à travers un UIViewRepresentable.

7
ph1psG

Veuillez vous référer à cet anwser en premier, puis vous comprendrez le code suivant que j'ai fait:

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    var body: some View {
        
        // text used in mask
        let text = Text("Text")
            .font(.system(size: 80, weight: .black, design: .rounded))
            .scaledToFit()                   // center text in view
        
        // container
        return ZStack {
            // background color
            Color.white.grayscale(0.3)
            // text card
            Gradient.diagonal(.yellow, .green)      // my custom extension 
                .inverseMask(text)                    // ⭐️ inverse mask
                // shadow for text
                .shadow(color: Color.black.opacity(0.7), radius: 3, x: 3, y: 3)
                .frame(width: 300, height: 200)
                // highlight & shadow
                .shadow(color: Color.white.opacity(0.9), radius: 18, x: -18, y: -18)
                .shadow(color: Color.black.opacity(0.3), radius: 14, x:  14, y:  14)
        }
    }
}

PlaygroundPage.current.setLiveView(ContentView())

et le résultat est:

enter image description here

L'extension de clé utilisée dans le code ci-dessus est .inverseMask():

import SwiftUI

extension View {
    // view.inverseMask(_:)
    public func inverseMask<M: View>(_ mask: M) -> some View {
        // exchange foreground and background
        let inversed = mask
            .foregroundColor(.black)  // hide foreground
            .background(Color.white)  // let the background stand out
            .compositingGroup()       // ⭐️ composite all layers
            .luminanceToAlpha()       // ⭐️ turn luminance into alpha (opacity)
        return self.mask(inversed)
    }
}

----[Édité]-----

Mon extension personnalisée pour Gradient:

import SwiftUI

extension Gradient {
    
    // general linear gradient ---------------------------
    
    public static func linear(
        from start: UnitPoint, 
        to     end: UnitPoint, 
        colors    : [Color]       // use array
    ) -> LinearGradient 
    {
        LinearGradient(
            gradient  : Gradient(colors: colors), 
            startPoint: start, 
            endPoint  : end
        )
    }
    
    public static func linear(
        from start: UnitPoint, 
        to     end: UnitPoint, 
        colors    : Color...     // use variadic parameter
    ) -> LinearGradient 
    {
        linear(from: start, to: end, colors: colors)
    }
    
    // specialized linear gradients ------------------------
    
    // top to bottom
    public static func vertical(_ colors: Color...) -> LinearGradient {
        linear(from: .top, to: .bottom, colors: colors)
    }
    
    // leading to trailing
    public static func horizontal(_ colors: Color...) -> LinearGradient {
        linear(from: .leading, to: .trailing, colors: colors)
    }
    
    // top leading to bottom trailing
    public static func diagonal(_ colors: Color...) -> LinearGradient {
        linear(from: .topLeading, to: .bottomTrailing, colors: colors)
    }
    
    // top leading to bottom trailing
    public static func diagonal2(_ colors: Color...) -> LinearGradient {
        linear(from: .bottomLeading, to: .topTrailing, colors: colors)
    }
}
1
lochiwei

En fait, même si cela semble être un hack à vous, c'est comment Swifti fonctionne.

Vous pouvez éviter ce "hack" en créant une vue personnalisée

Un exemple pourrait être:

public struct BackgroundedText: View {

    var first_color = Color.green
    var second_color = Color.white
    var text_color = Color.green

    var size = CGSize(width: 200, height: 100)
    var xOffset: CGFloat = 50
    var yOffset: CGFloat = 50

    var text = "Hello world!"

    init(_ txt: String, _ txt_color: Color, _ fColor: Color, _ sColor: Color, _ size: CGSize, _ xOff: CGFloat, _ yOff: CGFloat) {
        self.text = txt
        self.text_color = txt_color
        self.first_color = fColor
        self.second_color = sColor
        self.size = size
        self.xOffset = xOff
        self.yOffset = yOff
    }


    public var body: some View {
        ZStack{
            Rectangle()
                .frame(width: self.size.width,
                       height: self.size.height)
                .foregroundColor(self.first_color)

            Rectangle()
            .frame(width: self.size.width - xOffset,
                   height: self.size.height - yOffset)
            .foregroundColor(self.second_color)

            Text(self.text)
                .foregroundColor(self.text_color)

        }
    }
}

Vous pouvez donc utiliser la vue de cette manière:

struct ContentView: View {
    var body: some View {
        BackgroundedText("Hello", .green, .green, .white, CGSize(width: 200, height: 100), 50, 50)
    }
}

Si vous le souhaitez, vous pouvez redimensionner le rectangle en fonction du texte à l'intérieur.

1
Andrew21111

J'ai eu une exigence similaire. En fait, mon exigence était que le texte est un texte multiligne qui défile pour révéler une ligne à la fois (chronométré avec quelqu'un racontant le texte. L'arrière-plan était une image.

J'ai résolu la façon qui suit. Un zstack avec l'image en premier, puis la couche de texte avec le texte multiligne positionnée de la manière dont je le veux, puis une autre couche de l'image avec un trou rectangulaire fait où je veux que le texte apparaisse. L'approche peut répondre à vos besoins - vous devrez positionner le trou, changer les couleurs, etc. pour répondre à vos besoins. Le code montre un trou rectangle d'environ trois quarts du chemin.

struct TestView : View {    
var body: some View {
    GeometryReader { proxy in
        ZStack {
            Image("MyImage")
                .resizable()
                .scaledToFit()
            Text("Multiline\ntext\nfor\nscrolling")
                .font(.title).foregroundColor(.white)
                .position(x: proxy.size.width * 0.5, y: proxy.size.height * 0.75 )
            Image("MyImage")
            .resizable()
            .scaledToFit()
                .mask(self.makeMask(for: proxy.size))
        }
    }
}
func makeMask(for sz : CGSize) -> some View {
    return VStack(spacing: 0) {
        Rectangle().fill(Color.black)
            .frame(height: sz.height * 0.75 + 4)
        Rectangle().fill(Color.clear)
            .frame(height: 40)
        Rectangle().fill(Color.black)
            .frame(height: sz.height * 0.25 - 40)
    }
}

}

0
Arasu Arasakumaran