Text opacity gradient - swift

I'm looking to make text to appear to fade out on the edge. Here is what I have so far:
struct ContentView: View {
var body: some View {
ZStack {
Color.red
Text("Hello world!")
// .blendMode(.luminosity) // <- Here?
.overlay(
LinearGradient(colors: [.white.opacity(0), .white], startPoint: .leading, endPoint: .trailing)
.frame(width: 50)
// .blendMode(.exclusion) // <- Here?
.frame(maxWidth: .infinity, alignment: .trailing)
)
}
}
}
Producing:
You can see the white gradient over the right side of the text. This should act like an opacity filter. Where the white isn't over, the text is fully visible. Below the white, the text should be completely transparent. It should be a gradual transition by using the gradient.
I think this can be solved using blendMode(_:), but I'm not too sure. I don't know which mode to use, nor where to apply it.
The solution to this should work in light & dark mode (i.e. the text may be black or white). The actual background behind is not a constant color (such as red) so using a red gradient overlay is not what I'm looking for.

Just use .mask instead.
struct ContentView: View {
var body: some View {
ZStack {
Color.red
Text("Hello world!")
/// for iOS 14 and below, replace { } with ( )
.mask {
/// no need for .opacity, just use .clear
LinearGradient(colors: [.white, .clear], startPoint: .leading, endPoint: .trailing)
}
}
}
}
Result:

With thanks to aheze's answer, it now works.
Just a small thing I'll add, to show the way to get the exact same results as in the question (because mask sort of inverts it):
struct ContentView: View {
var body: some View {
ZStack {
Color.red
Text("Hello world!")
.mask(
HStack(spacing: 0) {
Color.white
LinearGradient(colors: [.white, .clear], startPoint: .leading, endPoint: .trailing)
.frame(width: 50)
}
)
}
}
}
Result:

Related

Swift UI Gradient make Spacer useless

I was creating a centered card with a background gradient, I would like to have the height acording with the content, like this:
The problem is when I use a gradient component inside the card, because the card height is taking the free space and it is ignoring the Spacer outside of the VStack
import SwiftUI
struct TunedModable<Content: View>: View {
#ViewBuilder var content:() -> Content
var body: some View {
VStack {
Spacer()
ZStack {
LinearGradient(
gradient: Gradient(
colors: [.gray, .green]
),
startPoint: .bottom,
endPoint: .top
)
VStack {
content()
}
}
.frame(maxWidth: .infinity)
.background()
.clipShape(
RoundedRectangle(cornerRadius: 25.0, style: .continuous)
)
Spacer()
Text("Testing").foregroundColor(.white)
}.frame(minWidth:0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).padding().background(.indigo)
}
}
struct TunedModable_Previews: PreviewProvider {
static var previews: some View {
TunedModable {
Text("Hello")
}
}
}
Result with gradient is a Card with full height but I need to have the height like "automatic" like the first picture
Instead of ZStack use background, like
content()
.background(
LinearGradient(
gradient: Gradient(
colors: [.gray, .green]
),
startPoint: .bottom,
endPoint: .top
))
ZStack extended to biggest view (which is Gradient because it does not have own size and consumes all available space), whereas background or overlay fit to size of target view (content in your case).
See also https://stackoverflow.com/a/63446622/12299030

Ignore safe area in safe area

I'm trying to use safeAreaInset(edge:alignment:spacing:content:), to create a floating view at the bottom. The green view should ignore the bottom safe area, but this isn't working.
Code:
struct ContentView: View {
var body: some View {
ScrollView {
LinearGradient(
colors: [.red, .blue],
startPoint: .top,
endPoint: .bottom
)
.frame(height: 1000)
}
.safeAreaInset(edge: .bottom) {
Color.green
.frame(height: 50)
.ignoresSafeArea(edges: .bottom)
}
}
}
Current result:
How do I make the green view ignore the bottom safe area so it touches the bottom?
Once you spoke about keyboard you add another difficulty to your original question! I just made simple code with overlay method it can easily converted to ZStack method as well! You do which way you like! Also I just add some fun on green View just for show and need refactor for sure, but that is not the part of issue or question!
struct ContentView: View {
#State private var string: String = String()
var body: some View {
let myDivider = Spacer().overlay(Color.secondary.frame(width: 1, height: 40))
Color.clear
.ignoresSafeArea(.all)
.overlay(
ScrollView {
ZStack {
LinearGradient(gradient: .init(colors: [.red, .blue]), startPoint: .top, endPoint: .bottom)
.frame(height: 1000)
VStack {
Spacer()
TextField("Enter youer text here ...", text: $string)
Spacer()
}
}
}
)
.overlay(
Color.green
.overlay(HStack { Spacer(); Text("🙈").padding(20); myDivider; Text("🙉").padding(20); myDivider; Text("🙊").padding(); Spacer() }.font(Font.system(.largeTitle)))
.frame(height: 80)
, alignment: .bottom)
.ignoresSafeArea(.container)
}
}
Creating an overlay(alignment:content:) after ignoring the safe area works. A view such as Color.clear can be used as the base.
Code:
struct ContentView: View {
var body: some View {
ScrollView {
LinearGradient(
colors: [.red, .blue],
startPoint: .top,
endPoint: .bottom
)
.frame(height: 1000)
}
.safeAreaInset(edge: .bottom) {
Color.clear
.frame(height: 50)
.ignoresSafeArea(edges: .bottom)
.overlay(
Color.green
)
}
}
}
Result:
And if you have content and want a background in the safe area:
Color.red
.frame(height: 50)
.ignoresSafeArea(edges: .bottom)
.background(
Color.green
)
Result:
Note: be careful with the 2nd version. If you use modifiers such as clipShape on the background, it can cause it to break again. Fix this by using .ignoresSafeArea(edges: .bottom) after you may clip or anything else which breaks this.

Animation transition of one gradient to another SwiftUI

I was trying to change the gradient of a background depending on the value of a state value which should be possible, and it is. However, I wanted to animate this change of gradient/background (as you often do when a property depends on some transition). So I tried setting up a ternary operator that would change the background to a different gradient, with an animation/transition. The background does change, but there is no smooth transitions, simply a harsh change. Here is a minimal working example I created to illustrate the issue:
import SwiftUI
// Based on https://nerdyak.tech/development/2019/09/30/animating-gradients-swiftui.html
struct ContentView: View {
#State var animCheck: Bool = false
var body: some View {
VStack {
Button(action: {
self.animCheck = true
}){
Text("Change gradient")
}
Button(action: {
}){
Text("random button")
.background(self.animCheck == true ? LinearGradient(gradient: Gradient(colors: [.white, .green]), startPoint: .leading, endPoint: .trailing).transition(.slide).animation(.spring()) : LinearGradient(gradient: Gradient(colors: [.black, .orange]), startPoint: .leading, endPoint: .trailing).transition(.slide).animation(.spring()) )
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Is there any way to get around this/create the intended effect of an animated background change here?
Thanks.
We need to animate container to make transition work, so here is a solution:
Text("random button")
.background(
VStack {
if self.animCheck {
LinearGradient(gradient: Gradient(colors: [.white, .green]), startPoint: .leading, endPoint: .trailing)
.transition(.slide)
} else {
LinearGradient(gradient: Gradient(colors: [.black, .orange]), startPoint: .leading, endPoint: .trailing)
.transition(.slide)
}
}.animation(.spring()))

ContextMenu on a rounded LinearGradient produces sharp edges in SwiftUI

I have the following view:
struct ContentView: View {
var body: some View {
LinearGradient(gradient: Gradient(colors: [.blue, .red]), startPoint: .topTrailing, endPoint: .bottomLeading)
.cornerRadius(16)
.frame(width: 140, height: 140)
.contextMenu {
Button("", action: {})
}
}
}
However, when the ContextMenu is invoked, the edges are not rounded:
I've tried several things, such as:
Applying the clipShape modifier to clip it to a RoundedRectangle
Wrapping the gradient as the background of a RoundedRectangle view
Using a Color instead of a LinearGradient (which works as expected, but not what I need)
However none work. Any advice would be appreciated, thanks!
Add the following code after .frame(...):
.contentShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
Updated for Swift 5.7.2
TLDR:
// Use the method that takes in ContentShapeKinds
.contentShape(.contextMenuPreview, RoundedRectangle(cornerRadius: 30))
Placed after .frame(...), and before .contextMenu { ... }
Detailed example:
var body: some View {
VStack(alignment: .leading, spacing: .zero) {
Text("Hello")
.foregroundColor(.white)
.font(.title)
}
.frame(maxWidth: .infinity, minHeight: 120)
// These corner radii should match
.background(RoundedRectangle(cornerRadius: 30).foregroundColor(.blue))
.contentShape(.contextMenuPreview, RoundedRectangle(cornerRadius: 30))
.contextMenu {
Button("Look!") { print("We did it!") }
}
}
The above code produces a contextMenu that looks like this:
... instead of this:

How can I create an "unfilled frame" in SwiftUI?

I'm sure this is something super-simple, but I cannot seem to figure it out. I am trying to create a "widget" that consists of three lines of text, stacked vertically. This information should be placed inside a "frame" or "border" so it resembles a card. There will be a row of these cards that will scroll horizontally.
Believe it or not, the only part of this I cannot figure out is how to draw the border around the widget. I've tried .border, but that snugs the border right up against the text. I know I can add padding, but what I really need is a fixed-size card so each element in the scrolling list is identical.
I've come closest using this:
.frame(width: geometry.size.width/1.3, height: 200)
.background(Color.white)
.border(Color.blue)
.cornerRadius(20)
...but the corners are all clipped. For reference, here's the complete code listing:
struct AccountTile: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 8) {
Text("Account Balance").font(.largeTitle)
Text("Account Name").font(.headline)
HStack(spacing: 0) {
Text("There are ").font(.caption).foregroundColor(.gray)
Text("6 ").font(.caption).fontWeight(.bold).foregroundColor(.blue)
Text("unreconciled transactions.").font(.caption).foregroundColor(.gray)
}
}
.frame(width: geometry.size.width/1.3, height: 200)
.background(Color.white)
.border(Color.blue)
.cornerRadius(20)
}
}
}
...and here's what that code is producing:
This is almost what I'm looking for - I just need the border to be complete.
Use some padding and overlay to create your border. Here is the code (:
struct AccountTile: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 8) {
Text("Account Balance").font(.largeTitle)
Text("Account Name").font(.headline)
HStack(spacing: 0) {
Text("There are ").font(.caption).foregroundColor(.gray)
Text("6 ").font(.caption).fontWeight(.bold).foregroundColor(.blue)
Text("unreconciled transactions.").font(.caption).foregroundColor(.gray)
}
}.frame(width: geometry.size.width/1.3, height: 200)
.background(Color.white)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.blue, lineWidth: 2))
}
}
}