SwiftUI: underlined text does not work with background material - swift

When I tried to display an underlined text with background material, I faced a problem
After some work around, I succeeded in creating the minimal test case that does not work as I expect
Could you please tell me, that is my wrong expectations, or I just missed something?
BTW: That works fine with other text modifiers, such as .bold() or .italic()
Here is an example to reproduce the bug:
var body: some View {
ZStack {
LinearGradient(colors: [.orange, .yellow, .red], startPoint: .topLeading, endPoint: .bottomTrailing)
.ignoresSafeArea()
Text("Some underlined text")
.underline()
.padding()
.background(.ultraThinMaterial)
}
}
Works fine:
var body: some View {
ZStack {
LinearGradient(colors: [.orange, .yellow, .red], startPoint: .topLeading, endPoint: .bottomTrailing)
.ignoresSafeArea()
Text("Some underlined text")
.underline()
.padding()
.background(.white)
}
}

var body: some View {
ZStack {
LinearGradient(colors: [.orange, .yellow, .red], startPoint: .topLeading, endPoint: .bottomTrailing)
.ignoresSafeArea()
Text("Some underlined text")
.underline()
.padding()
.background(
Rectangle() //Add this one line of code
.fill(.ultraThinMaterial)
)
}
}
Add this one to your code will fix your problem.

It's probably looks like bug. But you can use next modifier to achieve normal behavior
struct Blur: UIViewRepresentable {
var style: UIBlurEffect.Style = .systemMaterial
func makeUIView(context: Context) -> UIVisualEffectView {
return UIVisualEffectView(effect: UIBlurEffect(style: style))
}
func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
uiView.effect = UIBlurEffect(style: style)
}
}
Implement it to your text view
Text("Some underlined text")
.underline()
.padding()
.background(Blur(style: .systemUltraThinMaterial))

Related

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 do you blur the background in a SwiftUI macOS application?

I want to make the highlighted section transparent and blurred similar to other macOS applications. I found articles online on how to use an NSViewController to blur which I don't fully understand. I am new to swift and don't yet understand how to use Viewcontrollers. My code is below. Any help would be appreciated!
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
GeometryReader { geometry in
NavigationView{
HStack(spacing: 0) {
ZStack{
Text("BitMessenger")
.font(.title)
.fontWeight(.light)
.foregroundColor(Color.white)
}
.frame(width: geometry.size.width/2, height: geometry.size.height+20)
.background(Color(red: 0.07, green: 0.07, blue: 0.07, opacity: 1.0))
VStack{
HStack {
Text("Sign Up")
.font(.headline)
.padding(.top, 30.0)
Spacer()
}
HStack {
Text("Welcome to BitMessenger")
.font(.subheadline)
.foregroundColor(Color.gray)
.padding(.top, 10.0)
Spacer()
}
Form {
VStack{
HStack {
Text("Full Name")
.font(.caption)
.foregroundColor(Color.white)
.padding(.top, 10.0)
Spacer()
}
TextField("ex. John Doe", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
HStack {
Text("Email Address")
.font(.caption)
.foregroundColor(Color.white)
.padding(.top, 10.0)
Spacer()
}
TextField("doejohn#example.com", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
HStack {
Text("Password")
.font(.caption)
.foregroundColor(Color.white)
.padding(.top, 10.0)
Spacer()
}
TextField("AIOFHWaowhf", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
HStack {
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Register")
.padding(.horizontal, 10.0)
}
.padding(.all)
}
}
}
.padding(.top)
Spacer()
NavigationLink(destination: ContentView()) {
Text("Already have an Account? Login")
.font(.caption)
.foregroundColor(Color.gray)
.background(Color.clear)
}
.padding(.bottom)
.foregroundColor(Color.clear)
}
.padding(.horizontal, 30.0)
.frame(width: geometry.size.width / 2, height: geometry.size.height+20)
.background(Color.black.opacity(0.9))
}.edgesIgnoringSafeArea(.all)
}
}
.edgesIgnoringSafeArea(.all)
.frame(width: 750.0, height: 500.0)
}
}
}
class MyViewController: NSViewController {
var visualEffect: NSVisualEffectView!
override func loadView() {
super.loadView()
visualEffect = NSVisualEffectView()
visualEffect.translatesAutoresizingMaskIntoConstraints = false
visualEffect.material = .dark
visualEffect.state = .active
visualEffect.blendingMode = .behindWindow
view.addSubview(visualEffect)
visualEffect.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
visualEffect.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
visualEffect.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
visualEffect.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You don't really need to subclass NSViewController. What you need is - NSVisualEffectView from AppKit,
A view that adds translucency and vibrancy effects to the views in your interface.
Since the NSVisualEffectView is not yet available in SwiftUI, you can wrap it using NSViewRepresentable pretty much like every AppKit control not available in SwiftUI.
You can do it like this -
import SwiftUI
struct VisualEffectView: NSViewRepresentable
{
let material: NSVisualEffectView.Material
let blendingMode: NSVisualEffectView.BlendingMode
func makeNSView(context: Context) -> NSVisualEffectView
{
let visualEffectView = NSVisualEffectView()
visualEffectView.material = material
visualEffectView.blendingMode = blendingMode
visualEffectView.state = NSVisualEffectView.State.active
return visualEffectView
}
func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context)
{
visualEffectView.material = material
visualEffectView.blendingMode = blendingMode
}
}
You can then use it as a standalone View-
VisualEffectView(material: NSVisualEffectView.Material.contentBackground, blendingMode: NSVisualEffectView.BlendingMode.withinWindow)
or use it as a background modifier to your highlighted VStack like this -
.background(VisualEffectView(material: NSVisualEffectView.Material.contentBackground, blendingMode: NSVisualEffectView.BlendingMode.withinWindow))
Go through the Apple Developer docs to learn more about the two blending modes. Also, play with the Material property to get the desired result.
This solution allows you to create a semiOpaqueWindow() type method that you can apply to a child of the View protocol, for example to a Rectangle shape here.
import SwiftUI
extension View {
public static func semiOpaqueWindow() -> some View {
VisualEffect().ignoresSafeArea()
}
}
struct VisualEffect : NSViewRepresentable {
func makeNSView(context: Context) -> NSView {
let view = NSVisualEffectView()
view.state = .active
return view
}
func updateNSView(_ view: NSView, context: Context) { }
}
struct ContentView : View {
var body: some View {
ZStack {
Rectangle.semiOpaqueWindow()
Text("Semi Transparent MacOS window")
}
}
}

ScrollView causes buggy buttons in SwiftUI

I've been trying to create a form in SwiftUI but because of the limitations using "Form()" I instead chose to create a ScrollView with a ForEach loop that contains a button. When I run the project and I try to click on the buttons it clicks the incorrect one, unless I scroll the view. I am new in SwiftUI and I have not been able to figure it out.
I tried making the ScrollView in different sizes and it doesn't seem to be related
struct DropDown: View {
var datos: [String]
var categoria: String
#State var titulo: String = "Seleccione"
#State var expand = false
var body: some View {
VStack {
Text(categoria).fontWeight(.heavy).foregroundColor(.white)
HStack {
Text(titulo).fontWeight(.light).foregroundColor(.white)
Image(systemName: expand ? "chevron.up" : "chevron.down").resizable().frame(width: 10, height: 6).foregroundColor(.white)
}.onTapGesture {
self.expand.toggle()
}
if expand {
ScrollView(showsIndicators: true) {
ForEach(0..<self.datos.count){ nombre in
Button(action: {
print(self.datos[nombre])
self.titulo = self.datos[nombre]
self.expand.toggle()
diccionarioDatos[self.categoria] = self.titulo
print(diccionarioDatos)
}) {
Text(self.datos[nombre]).foregroundColor(.white)
}
}
}
.frame(maxHeight: 150)
.fixedSize()
}
}
.padding()
.background(LinearGradient(gradient: .init(colors: [.blue, .green]), startPoint: .top, endPoint: .bottom))
.cornerRadius(20)
.animation(.spring())
}
}
I clicked on "2018" under "Modelo" and "2015" got selected for some reason
This is how the dropdown menu looks like
As I tested the observed behaviour is due to order of animatable properties. In your case moving rounding corners into background itself solves the problem.
So instead of
.background(
LinearGradient(gradient: .init(colors: [.blue, .green]), startPoint: .top, endPoint: .bottom)
)
.cornerRadius(20)
Use
.background(
LinearGradient(gradient: .init(colors: [.blue, .green]), startPoint: .top, endPoint: .bottom)
.cornerRadius(20)
)
Consider using .onTapGesture instead of action for the buttons.
Button(action: {}, label: {
Text(self.datos[nombre]).foregroundColor(.white)
}).onTapGesture{
print(self.datos[nombre])
self.titulo = self.datos[nombre]
self.expand.toggle()
diccionarioDatos[self.categoria] = self.titulo
print(diccionarioDatos)
}