SwiftUI Finding Solution for Button overlay on Image - swift

I overlayed a button/text on the image, but the button didnt respond. Is there any solution?
It seems like the hidden navigation bar view is blocking the button or the image is blocking the touch gesture. How should I fix this?
struct Tutorial: View {
let dataModel = DataModel()
#State var messageIndex = 0
var body: some View{
ZStack{
Image("Tutorial UI")
.resizable()
.scaledToFill()
.overlay(
VStack(spacing:20){
VStack(spacing:30){
HStack{
Spacer()
Text("Next")
.foregroundColor(.blue)
.fontWeight(.black)
.onTapGesture(count:1) {
if self.messageIndex != 4{
self.messageIndex += 1
}else{
self.messageIndex = 0
}
}
}
Text(dataModel.tutorialMessage[messageIndex][0])
.fontWeight(.black)
.font(.largeTitle)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
}.padding()
Text(dataModel.tutorialMessage[messageIndex][1])
.font(.title)
Spacer()
}.padding(.horizontal, 48)
.padding(.top, 50)
)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity,
alignment: .center)
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
}

What #Asperi was getting at is you have provided a code snippet that depends upon a model we know nothing about. When you post code, you should post a simplified version. In this case I took your code and swapped out your data model for a simple array. I also downloaded an image to use from NASA's Image of the Day to use. And, Asperi nailed the problem with his second question...
Looking at it, the first thing that struck me is you are trying too hard to position things just so in SwiftUI. It doesn't work that way. And that led to your problems. I pulled out your frames which actually don't do anything in this layout. I also pulled out the specific padding amounts. I then pulled the Spacer() out that was in your HStack, and your "Next" button appeared on the screen. I suspect that setting the image as .scaledToFill() and an image that is not in the same dimensions of the screen caused the view to be larger than the screen you could see. Putting the Spacer() in caused your "Next" button to move all of the way to the right, which was off screen. Once I could see it, I was able to click it and it worked fine.
You have an extremely complicated structure that produces a pretty simple layout. Next time, start small and only add what you need. Less layout is better in SwiftUI, or you end up with unintended consequences. Try changing the HStack in your Overlay to the following and then work from there, one step at a time:
HStack{
Text("Next")
.foregroundColor(.blue)
.fontWeight(.black)
.onTapGesture(count:1) {
if self.messageIndex != 4{
self.messageIndex += 1
}else{
self.messageIndex = 0
}
}
}
Remember, in SwiftUI the Child View determines its own size. Don't try to force things from a Parent.

Related

How do I avoid app zoom when keyboard opens?

When I click my SwiftUI text field and the keyboard opens, the app zooms out (shown in video).
I have two questions about this behaviour:
Why does this happen?
How do I avoid this happening?
Here is my code:
struct BestillView: View { // This view is put inside a tab view with .ignoresSafeArea
#State var navn = ""
#State var varsling = true
var body: some View {
NavigationView {
ZStack {
Color("BackgroundColor")
.ignoresSafeArea()
VStack {
Image("Liquid") // This is my image overlayed on the background, i suspect this may be the only element that actually gets zoomed out
.resizable()
.aspectRatio(contentMode: .fit)
.ignoresSafeArea()
Spacer()
}
VStack {
ZStack(alignment: .leading) { // This is where the text field i'm having trouble with is
Color("UnselectedColor")
.frame(height: 50)
.cornerRadius(20.0)
if navn.isEmpty { // I have a separate text element as the placeholder text so i can give it a custom color
Text("Navn")
.foregroundColor(Color("AccentColor"))
.padding()
}
TextField("", text: $navn)
.padding()
}
.frame(width: 300)
Spacer()
.frame(height: 20.0)
// I removed the rest of my code, I don't think it should be necessary in this question - it's only a NavigationLink and a Toggle
}
}
}
}
}
You have .ignoresSafeArea() on your Image, but you actually need it on the VStack that contains the Image. The VStack is shrinking to fit the keyboard’s safe area, which squeezes the image too.
The view is actually not shrinking; the image is shrinking - because as the view moves up, it has less height to fit.
You can update your code as:
Image("Liquid")
.aspectRatio(contentMode: .fill)
and it will keep the size same - as the width will remain same.

Text goes off screen after using .aspectRatio(contentMode: .fill)

I am trying to make an app with a background image using SwiftUI. However, the image is not the same aspect ratio as the screen, making me use .aspectRatio(contentMode: .fill) to fill the entire screen with it. This works completely fine until I start adding text. When adding text, it now goes off the screen instead of wrapping like it normally should do.
Here's my code:
struct FeaturesView: View
{
var body: some View
{
ZStack
{
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
VStack(alignment: .leading)
{
Text("Hello this is some sample text that i am writing to show that this text goes off the screen.")
}
.foregroundColor(.white)
}
}
}
And this is the preview:
As you can see, the text goes off the screen. I have tried using ´.frame()´ and specifying a width and height to fix it, but that causes issues when using the view inside other views. I am using the Xcode 12 beta.
I'm new to Swift and SwiftUI, so all help is appreciated :)
Of course it goes, because image expands frame of container, ie ZStack, wider than screen width.
Here is a solution - make image to live own life in real background and do not affect others. Tested with Xcode 12 / iOS 14.
var body: some View
{
ZStack
{
// ... other content
VStack(alignment: .leading)
{
Text("Hello this is some sample text that i am writing to show that this text goes off the screen.")
}
.foregroundColor(.white)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
)
}
this happen because your ZStack using width that equal to your image, try to wrap your image using VStack and geometryReader .
GeometryReader { geometry in
ZStack {
Image() // your image
VStack(alignment: .leading) {
Text("Hello this is some sample text that i am writing to show that this text goes off the screen.")
}.foregroundColor(.white)
}.frame(maxWidth: geometry.size.width)
}
I'm write this on my windows it might some spelling error

SwiftUI Mac Positioning view absolute inside other view

I am trying to position a view absolutely inside a other view in SwiftUI for MacOS.
I am basically having a VStack, with a ScrollView/ List inside. How can I position a view absolute in a corner. Even when the window gets shrinked it should stay there. It even may overlap with the content of the ListView.
I couldn't really figure it out how to do it. I used a different approach at the moment, with using a new VStack then use Spacer() and put a new HStack right at the bottom with an item aligned right. This works but basically don't overlap. The views are separated.
Here is a demo... I want to achieve that yellow circle. It can basically be an Image or another View..
Even when the window gets smaller, it will stay there:
Here is the code I used... it is just a simple scroll view with content.
ScrollView(content: {
HStack
{
Spacer()
Text("Test Test TestTest Test TestTest Test Test")
Spacer()
}
...
}).frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(.red))
It can be used ZStack as below
struct TestCircleWithList: View {
var body: some View {
ZStack(alignment: .bottomTrailing) {
ScrollView(content: {
HStack
{
Spacer()
Text("Test Test TestTest Test TestTest Test Test")
Spacer()
}
}).frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(.red))
Circle().fill(Color.yellow)
.frame(width: 80, height: 80)
.padding(20)
}
}
}

List inside ScrollView is not displayed on WatchOs

I have a list inside a scrollview and it is not displaying below the image and the buttons. I've also tried to put the list and other items inside of a VStack and that allows me to see one item of the list at the time opposed to scrolling past the Image and buttons to show the whole list.
ScrollView{
Image(uiImage: self.image)
.resizable()
.frame(width: 80, height: 80)
.scaledToFit()
Text("\(name)")
.lineLimit(2)
HStack{
Button(action: {
print("button1")
}){
Image(systemName: "pencil")
}
Button(action: {
print("button 2")
}){
Image(systemName: "trash")
}
}
List{
ForEach(self.items, id: \.self) { item in
VStack{
Text(item.name)
.font(.headline)
.lineLimit(1)
Text(item.subname)
.font(.subheadline)
.lineLimit(1)
}
}
}
}
.navigationBarTitle(Text("Tittle"))
.edgesIgnoringSafeArea(.bottom)
Ive also tried to add .frame( minHeight: 0, maxHeight: .infinity)
to the list to force it to have its whole height and that did not work either. Any suggestions or might this be a swiftUI bug ?
EDIT
I just realized Im getting this error when scrolling:
APPNAME Watch Extension[336:60406] [detents] could not play detent NO, 2, Error Domain=NSOSStatusErrorDomain Code=-536870187 "(null)", (
{
Gain = "0.01799999922513962";
OutputType = 0;
SlotIndex = 4;
},
{
Gain = "0.6000000238418579";
OutputType = 1;
SlotIndex = 5;
}
)
Indicate some height for your List like
List{
ForEach(self.items, id: \.self) { item in
VStack{
Text(item.name)
.font(.headline)
.lineLimit(1)
Text(item.subname)
.font(.subheadline)
.lineLimit(1)
}
}
}.frame(minHeight: 200, maxHeight: .infinity)
Have you tried putting the List inside of a GeometryReader and setting the frame there?
I got the same error with UIKit. The error message is related to haptic feedback, see here.
It says that a haptic feedback could not be played, probably type 2, i.e. directionDown, see here.
Since my code does not call play(_:), it must be called by watchOS itself.
The reason for the error might be very fast scrolling, which could result in too frequent calls to play(_:) that cannot be handled properly. The docs say:
Do not call this method multiple times in quick succession. If the
haptic engine is already engaged when you call this method, the system
stops the current feedback and imposes a minimum delay of 100
milliseconds before engaging the engine to generate the new feedback.
If this is really an effect of watchOS, I guess you cannot do anything to avoid the error.

Making Button span across VStack

I currently have the following SwiftUI view:
HStack {
...
VStack {
TextField { ... }
SecureField { ... }
Button { ... }
}
...
}
I've added a .background(Color.green) to the Button, and as you can see, the view is very snug to the text.
I'm wondering if there's a way to adjust the width of the button so that it fills across VStack - something like a .fill mode for UIStackView.
The best way to do this is via .frame(maxWidth: .infinity)
https://developer.apple.com/documentation/swiftui/view-layout
If you want the button not to be centered you need to specify alignment.
e.g.: .frame(maxWidth: .infinity, alignment: .leading)
Button(action: handleSignInAction) {
Text("Sign In")
}
.frame(maxWidth: .infinity)
.background(Color.green)
Old answer from 2019:
You could use a HStack with a Text and Spacer to get a Button that fills the width of its parent:
Button(action: handleSignInAction) {
HStack {
Spacer()
Text("Sign In")
Spacer()
}
}.background(Color.green)
#d.felber's answer is almost complete, but you'd need a Spacer() on each side to center:
Button(action: {
// TODO: ...
}) {
HStack {
Spacer()
Text("Sign In")
Spacer()
}
}
.frame(maxWidth: .infinity)
Did the trick for me.
If you would like to stick to the method SwiftUI docs suggest you need to use GeometryReader and set the buttons width manually. Geometry reader updates its properties for different devices and upon rotation.
GeometryReader { geometry in
Button().frame(width: geometry.size.width, height: 100)
}
Use frame(maxWidth: .infinity) inside the Button like this:
Button(action: {...}) {
Text("Sign In")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.tint(.green)
Not like this, because the tappable area doesn't stretch:
Button(action: {}) {
Text("Sign In")
}
.frame(maxWidth: .infinity)
.background(Color.green)
Make the button text's frame the size of the UIScreen and then set the background color after it (make sure all style changes are done after changing the frame size, otherwise the style changes will only be visible on the original default frame). The frame size will propagate upward to increase the width of the button to the width of the screen as well.:
Button(action: {
// Sign in stuff
}) {
Text("Sign In")
.frame(width: UIScreen.main.bounds.width, height: nil, alignment: .center)
.background(Color.green)
}
You can also add some negative horizontal padding in between setting the frame and background in order to offset from the edge of the screen:
Button(action: {
// Sign in stuff
}) {
Text("Sign In")
.frame(width: UIScreen.main.bounds.width, height: nil, alignment: .center)
.padding(.horizontal, -10.0)
.background(Color.green)
}
Something like this?
Button(action: {
// Do your login thing here
}) {
Capsule()
.frame(height: 44)
.overlay(Text("Login").foregroundColor(Color.white)).padding()
}
I am not sure if there is a .fill method similar to UIStackView but, if what you want to do is provide some spacing on the Button (or any view for that matter) what worked for me is either setting the frame or padding
.frame(width: 300, alignment: .center) (we can also set a height here but, if not it should be able to infer the height based on the button text.
If you don't want to set an arbitrary width, you can also import UIKit and make use of UIScreen and get the devices full width. (There may be a SwiftUI way of getting this but, haven't found it at this time yet)
.frame(width: UIScreen.main.bounds.width, alignment: .center)
and then you can add a little bit of padding for some breathing room
.padding(.all, 20)
The issue with the padding is that it will add onto the additional width of the screen so we would have to take into account when setting the width.
(20 * 2 sides from leading and trailing)
.frame(width: UIScreen.main.bounds.width - 40, alignment: .center)
(breathing room is for when the text covers to the end of the screen or if your alignment is .leading or .trailing)