Overlay text on Image SwiftUI and WidgetKit - swift

I'm currently creating a widget for my new iOS 14 app and I encountered a weird problem when I'm trying to add and Overlay with Text on top of my Image. The overlay is correctly working on a blank SwiftUI project but it's not working on the Widget.
Below you can find the code that I'm using. KFImage is from KingFisherSwiftUI, but if you want to test it, you can just add a static Image from the Asset or from the System.
Thanks!
struct NewItems: View {
var body: some View {
KFImage(URL(string: "https://img.freepik.com/free-vector/triangular-dark-polygonal-background_23-2148261453.jpg?size=626&ext=jpg")! )
.resizable()
//.frame(width: 200, height: 200, alignment: .center)
.overlay(TextView(), alignment: .bottomTrailing)
.cornerRadius(20)
}
}
struct TextView: View {
var body: some View {
ZStack {
Text("Hello World Beautiful wallpaper")
.foregroundColor(.white)
.shadow(color: .black, radius: 1, x: 1.0, y: 1.0)
.padding(6)
.opacity(0.8)
}
.background(Color.black)
.cornerRadius(10)
.padding(3)
}
}
[EDIT]
Below the result of the code. Looks like the image is on top of the text, even though it supposed to be in overlay.

In the end after multiple tries I managed to find an alternative solution.
struct TextView: View {
let entry: HelloWorldEntry
var body: some View {
ZStack {
Text("SOME TEXT")
.foregroundColor(.white)
.shadow(color: .black, radius: 1, x: 1.0, y: 1.0)
.padding(6)
.opacity(0.8)
}
.background(Color.black)
.cornerRadius(10)
.padding(6)
}
}
struct NewTest: View {
let entry: HelloWorldEntry
var body: some View {
VStack {}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
KFImage(URL(string: "https://example.com/image.png")!)
.resizable()
.scaledToFill()
)
.overlay(TextView(entry: entry), alignment: .bottomTrailing)
}
}

Related

NavigationView doesn't show up correctly in ScrollView

I have encountered this problem during UI building. Previously, I've been asking here about NavigationView and why NavigationLink wasn't working. I fixed this problem with NavigationView at the top of my ScrollView. Now FrameView works just fine: opens and closes it in preview, which means I'm doing it right.
Here's the catch: When I add this LongFrameScrollView to my main view HomeView, it appears in some sort of frame and doesn't open link in full screen.
What am I doing wrong? How to fix that? Providing the code:
// Long frame view
import SwiftUI
struct LongFrameView: View {
var body: some View {
NavigationLink {
PlayerView()
} label: {
ZStack {
Rectangle()
.fill(LinearGradient(gradient: Gradient(colors: [Color(red: 0.268, green: 0.376, blue: 0.587), Color(red: 0.139, green: 0.267, blue: 0.517)]),
startPoint: .leading,
endPoint: .trailing))
.frame(width: 310, height: 62)
.cornerRadius(8)
HStack {
Image("mountains")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 70, height: 62)
.cornerRadius(8, corners: [.topLeft, .bottomLeft])
VStack(alignment: .leading) {
Text("Sense of anxiety")
.font(.custom("Manrope-Bold", size: 14))
.foregroundColor(.white)
Text("11 MIN")
.font(.custom("Manrope-Medium", size: 12))
.foregroundColor(.white)
}
Spacer()
}
}
.frame(width: 310, height: 62)
}
}
}
// Scroll View with LongFrame. Works fine in the preview
struct LongFrameScrollView: View {
let rows = Array(repeating: GridItem(.fixed(70), spacing: 10, alignment: .leading), count: 2)
var body: some View {
NavigationView {
ScrollView(.horizontal, showsIndicators: false) {
LazyHGrid(rows: rows, spacing: 10) {
// PLACEHOLDER UNTIL SERVER IS READY
LongFrameView()
LongFrameView()
LongFrameView()
LongFrameView()
}
}
.padding([.horizontal, .bottom], 10)
}
}
}
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .center) {
///
// Long frame. There is more view, but we'll just ignore them
LongFrameScrollView()
.padding(.bottom)
///
}
.padding(.top, 10)
}
.background(
Image("background2")
.resizable()
.aspectRatio(contentMode: .fill)
)
}
}
The reason why it wasn't working correctly is because it was in TabView. So, in order to work, our TabView must be in NavigationView and we must delete from all child views NavigationView.

How to put an Image on top of a Color View in SwiftUI

I have an image that needs to be put in the top center of my view. How can I do so?
struct MyTextField: View
{
#ObservedObject var model: TextFieldModel
var body: some View
{
VStack(alignment: .leading, spacing: -60)
{
Color.black.position(CGPoint(x: 200, y: -50)).frame(width: 417, height: 120,
alignment: .top)
.background(Image(("appIconLogo")).resizable().frame(width: 50, height: 50))
//Spacer(minLength: 5)
VStack(alignment: .leading, spacing: 30)
{
Text("Welcome!")
.font(.system(size: 60, design: .rounded))
.foregroundColor(.black)
iTextField("Search Your Ticker", text: $model.text, isEditing: $model.isEditing)
.foregroundColor(.black)
.showsClearButton(true)
.onReturn
{
print($model.text)
NotificationCenter.default.post(name: Notification.Name(rawValue:
"isValidTicker"), object: nil)
}
.foregroundColor(.black)
.style(height: 58, font: nil, paddingLeading: 25, cornerRadius: 6,
hasShadow: true, image: Image(systemName: "magnifyingglass"))
.foregroundColor(.black)
}
}
.position(CGPoint(x: 200.0, y: 150.0))
.padding()
}
}
But my View looks like this. How can I move my image to the top center of the page where the black color is?
Here is a demo of possible layout. Prepared with Xcode 12.1 / iOS 14.1.
struct MyTextField: View
{
var body: some View
{
VStack {
Color.black
.frame(height: 120)
.overlay(Image(("plant")).resizable().frame(width: 50, height: 50))
.edgesIgnoringSafeArea(.top)
VStack(alignment: .leading, spacing: 30)
{
Text("Welcome!")
.font(.system(size: 60, design: .rounded))
.foregroundColor(.black)
.padding(.leading)
// .. other code
}.frame(maxWidth: .infinity, alignment: .leading)
}.frame(maxHeight: .infinity, alignment: .top)
}
}
It's a little confusing what you are trying to do in your code, but I think this might get you started. I made a background layer that extends to the edge of the screen, and then put a Content layer on top of it, where you can put the Image at the top of the VStack.
You should try to avoid setting exact width/height/CGPoints whenever possible in SwiftUI.
var body: some View {
ZStack {
// Background
Color.white
.edgesIgnoringSafeArea(.all)
// Content
VStack(spacing: 20) {
Image(systemName: "heart.fill") // Image here
Text("Welcome!") // Title
.font(.system(size: 60, design: .rounded))
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(.black)
TextField("Search your ticker", text: $textFieldText) // Textfield
.frame(height: 50)
.background(Color.gray)
.cornerRadius(10)
Spacer()
}
.padding()
}
}

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:

Align rectangle to be within certain distance of safe area bottom edge SWIFTUI

I am used to Interface Builder and layout constraints but now I want to convert my app to Swift UI. What I am trying to do right now is align the top edge of the view marked with a 1 to be within a certain distance of the safe area bottom edge (marked with a 2) so that the top edge that is now at 1 will then be at position 3. I tried using spacers but then it will look different on smaller devices such as an iPhone 8. In IB I could have used a simple layout constraint. How does this work in Swift UI? I have attached the relevant code and an image. Thank you for your help.
struct ContentView: View {
init() {
UINavigationBar.appearance().backgroundColor = .orange
}
var body: some View {
NavigationView {
VStack{
Spacer()
ZStack{
Rectangle()
.fill(Color(hue: 0, saturation: 0, brightness: 0, opacity: 0.1))
Image("")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 150)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
}.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width, alignment: .center)
Spacer(minLength: 100)
ZStack(alignment: .bottom){
ExtractedView()
.edgesIgnoringSafeArea(.bottom)
}
}
.navigationBarTitle(Text("You See"))
.navigationBarHidden(false)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone")
}
}
struct ExtractedView: View {
#State private var name: String = ""
var body: some View {
VStack{
ZStack(alignment: .top){
VStack{
RoundedRectangle(cornerRadius: 50)
.frame(width: 60, height: 7)
.padding(.top)
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Start advertising")
.font(.headline)
.foregroundColor(Color.black)
}.padding(.top)
TextField("Test", text: $name)
.padding(.all)
.background(Color.white.cornerRadius(20))
.padding()
}
RoundedRectangle(cornerRadius: 30)
.fill(Color(hue: 0, saturation: 0, brightness: 0, opacity: 0.1))
.zIndex(-5)
}
}
}
}
Ok, so I was able to solve my problem. The trick is to create two VStacks with different frame alignments. The outer VStack has top alignment so that the Discover view can be at the top. The inner VStack has a bottom alignment so that the sheet can be pulled up from the bottom. Space will be filled from the bottom up in this case.
VStack{
Discover()
.padding(.top, 60.0)
VStack{
Text("Recent Messages:")
.font(.headline)
DetailSheet()
.offset(x: 0, y: 0)
}.frame(width: g.size.width, height: (g.size.height - g.size.width - 80 + 200), alignment: .bottom)
}
.frame(width: g.size.width, height: g.size.height, alignment: .top)
you can try this:
offset -10 is just your offset you want to have....
i hope i understood you right, i am not so sure...
var body: some View {
VStack{
// ZStack(alignment: .top){
VStack{
RoundedRectangle(cornerRadius: 50)
.frame(width: 60, height: 7)
.padding(.top)
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Start advertising")
.font(.headline)
.foregroundColor(Color.black)
}.padding(.top)
TextField("Test", text: $name)
.padding(.all)
.background(Color.white.cornerRadius(20))
.padding()
}.background(RoundedRectangle(cornerRadius: 30)
.fill(Color(hue: 0, saturation: 0, brightness: 0, opacity: 0.1))
).offset(y:-10)
}
// }

SwiftUI: How to make entire shape recognize gestures when stroked?

I'm creating a custom button in SwiftUI using some shapes.
As a minimal example, I have a filled rectangle, enclosed by a stroked circle (no fill). This is wrapped in a ZStack and a TapGesture is added to that. It works, but my only issue is that the empty space between the square and the circle is not tappable.
How can I make everything inside the circle tappable, without adding a fill to the circle?
struct ConfirmButton: View {
var action: () -> Void
var body: some View {
ZStack {
Circle()
.stroke(Color.purple, lineWidth: 10.0)
.padding(5)
Rectangle()
.fill(Color.red)
.frame(width: 200, height: 200, alignment: .center)
}.gesture(
TapGesture()
.onEnded {
print("Hello world")
self.action()
}
)
}
}
You need to define the hit area, with modifier .contentShape():
struct ConfirmButton: View {
var action: () -> Void
var body: some View {
ZStack {
Circle()
.stroke(Color.purple, lineWidth: 10.0)
.padding(5)
Rectangle()
.fill(Color.red)
.frame(width: 200, height: 200, alignment: .center)
}.contentShape(Circle())
.gesture(
TapGesture()
.onEnded {
print("Hello world")
self.action()
}
)
}
}