Je veux atteindre suivi avec Swiftui:
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
.
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:
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)
}
}
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.
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)
}
}
}