SwiftUI overlay cancels touches - swiftui

I have a button and I'd like to put a semi-transparent gradient overlay on top of it.
Button(action: {
print("Pressed")
}) {
Text("Press me")
}
.overlay(
LinearGradient(
gradient: Gradient(colors: [.clear, Color.black.opacity(0.3)]),
startPoint: .top,
endPoint: .bottom
).disabled(true)
)
Even though the gradient has disabled(true) it still eats the touches and doesn't forward them to the actual button. .allowsHitTesting(false) provides the same result.
Any idea what can be wrong?
Note: I know I can put the overlay just to Text("Press me") but I don't want it. (This is just example code showcasing the problem)
Edit: This issue is solved in Xcode 11.2 ✅

The following code works on Xcode 11.2 / iOS 13.2
struct TestButtonWithOverlay: View {
var body: some View {
Button(action: {
print("Pressed")
}) {
Text("Press me")
.padding()
}
.overlay(
LinearGradient(
gradient: Gradient(colors: [.clear, Color.black.opacity(0.3)]),
startPoint: .top,
endPoint: .bottom
)
.allowsHitTesting(false) // !!! must be exactly here
)
}
}

When working with a ScrollView the correct fix is to use the .background content modifier on the ScrollView:
https://developer.apple.com/documentation/swiftui/form/background(alignment:content:)
It will as expected with your "background" appearing above the content of the ScrollView.
Example of using to create a shadow effect:
Interesting note: On the simulator .overlay works with a ScrollView, but on a physical device only .background allows scrolling.
The physical device is on iOS 14, and the simulator is iOS 15, so it's possible it might have been a regression. Although apparently this issue dates back further than either.

Related

Navigation View Formatting Trouble

I'm a newbie, using XCode 13.0 to create a very basic app that needs to have a Settings view. I'd like to navigate to the Settings view on tapping a label. To do that, it seemed sensible to use a NavigationView with a NavigationLink.
Unfortunately, I'm encountering a formatting issue that creates a mess of the HStack in which the Setting label (gear icon) resides, as show below:
This is what I want, a result of the following code:
HStack(spacing: 25) {
... other labels
Label ("", systemImage: "gear")
.foregroundColor(.gray)
.font(.title)
.onTapGesture(perform: {
// Set a state variable that triggers an extension
// that brings up the SettingsView
})
}
This is what happens when NavigationView encapsulates the gear icon label. Note the vertical and horizontal white space around it.
HStack(spacing: 25) {
... other labels
NavigationView {
NavigationLink(destination: SettingsView()) {
Label ("", systemImage: "gear")
.foregroundColor(.gray)
.font(.title)
}.navigationBarTitle(Text(""))
}
}
I've, literally, spent weeks (sporadically) on this issue, looking up dozens of answers and trying various formatting options, without luck. I've also tried encapsulating parent and grandparent stacks into the NavigationView. To no avail. Surely, this is something trivial. Can somebody point me in the right direction?
p.s. there are other issues in that that Navigation link opens as a sub-window; I plan to tackle that later.
Edit: Right, so I tried using Yrb's code:
HStack(spacing: 25) {
... other labels
NavigationView {
NavigationLink(destination: Text("Linked View")) {
Image(systemName: "gear")
.foregroundColor(.gray)
.font(.title)
}
.fixedSize()
.background(Color.red)
}
]
Unfortunately, there's no substantive change...
In diagnosing these sort of issues, it helps to throw a .background() with a color on. You can then see the issue. In this case, it was twofold, one, you need to use a .fixedSize to shrink the view to its smallest dimensions necessary. That would leave you with the icon plus a little space. That was due to you using a label as it was leaving a spot for the Text("") that you used as a fill in. Since you only want the image, use Image(systemName:) The code then comes out like this:
struct NavLinkNoSpace: View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("Linked View")) {
Image(systemName: "gear")
.foregroundColor(.gray)
.font(.title)
}
.fixedSize()
// Setting this shows you what space you are using. Remove it when you are done
.background(Color.red)
}
}
}
A couple more things. If you have not ever set the NavigationTitle, you don't need to set it to "". In your example, there was no title, so I simply removed it and there was no effect.
More importantly, and it was addressed by some of the comments, you should only have one NavigationView in the view hierarchy. As long as you are in the hierarchy, you do not need to wrap things like NavigationLink to have them work. You can always throw one around your view call in the preview provider if you are in a child view, to show what things look like, and to test NavigationLinks, etc., but do not just put them in to your main code. It will lead to undesirable outcomes.
To summarize what worked to fix the primary problem, that of formatting: The key was in figuring what to encapsulate within the NavigationView. My mistake was to assume that only the NavigationLink needed to be in the NavigationView.
What worked was to place all the contents of the body into the NavigationView, like below:
var body: some View {
NavigationView {
VStack(spacing: -10) {
Text(appName)
.font(.largeTitle)
.foregroundColor(.blue)
.padding(.bottom)
// ...
// includes a bunch of VStacks and HStacks
// ... and finally
NavigationLink(destination: SettingsView()) {
Image(systemName: "gear")
.foregroundColor(.gray)
.font(.title)
// ... more stuff
// ... and finally
}.padding(.top, -100) // NavigationView
} // body

How do I prevent SwiftUI's TabView from moving vertically when dragging? [duplicate]

I have set up a TabView in my application, so that I can swipe horizontally between multiple pages, but I also have an unwanted vertical scroll that may appear, with a bounce effect so. How can I disable this vertical scroll?
My code:
struct ContentView: View {
#State private var currentTabIndex: Double = 0
var body: some View {
VStack {
TabView(selection: $currentTabIndex) {
Text("Text n°1")
.tag(0)
Text("Text n°2")
.tag(1)
}
.border(Color.black)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
}
I had this same problem. It's not an exact solution, but you can turn off bouncing on scrollviews (which is used within a TabView). And as long as the items within the TabView are not larger than the TabView frame, it should act as if you disabled vertical scrolling.
I would call it either .onAppear or in your init function:
.onAppear(perform: {
UIScrollView.appearance().bounces = false
})
Note: this disables the bouncing on ALL scrollviews across your app... So you may want to re-enable it .onDisappear.
Still an issue with Xcode 12.4.
I managed to workaround that by wrapping the TabView within a ScrollView and using the alwaysBounceVertical property set to false, as follow:
ScrollView(.horizontal) {
TabView {
///your content goes here
}
.tabViewStyle(PageTabViewStyle())
}
.onAppear(perform: {
UIScrollView.appearance().alwaysBounceVertical = false
})
.onDisappear(perform: {
UIScrollView.appearance().alwaysBounceVertical = true
})
I actually came across this because I saw this effect in a tutorial but couldn’t replicate it on iOS 15.2. However, I managed to replicate it on iOS 14.4 on another simulator side by side. So I guess this behaviour is disabled or fundamentally changed in the newer iOS.
Demonstration

Corner radius makes the image vanish

I was experimenting with swiftUI and i found out that cornerRadius attributes makes my image disappear.
Image(uiImage: content.image)
.resizable()
.scaledToFill()
.frame(width: 60, height: 40)
.cornerRadius(3)
Is there something wrong with the code here ?
I want my image to be resizable, fill the whole view, have a dimension of 60x40 and a cornerRadius of 3.
Thanks for your help, if you know what's wrong :)
XCode: 12.1 (12A7403)
I came across the same problem and I think it is either a bug, or a something new that came with Xcode 12.1, because it was working perfectly a few weeks back. I did try a lot of ways to put the .cornerRadius() but it always resulted in the Image vanishing. This is my final solution, but I do believe .cornerRadius() would be better here. I hope it will help you! The color of the stroke will need to be the color of your background.
Image("MyImage")
.resizable()
.frame(width: 19, height: 19, alignment: .center)
.overlay(RoundedRectangle(cornerRadius: 4)
.stroke(Color.white, lineWidth: 3.3)
)
I have met similar problems. When I set the cornerRadius for a view on simulator, the view disappear. However, when running on the real device, the view shows up. I think it is a bug of Xcode, you may test on real device to see your view.
Here's how I worked around this.
I made an equivalent of ImageView with UIKit.
struct FixImageView: UIViewRepresentable {
let image: UIImage
let contentMode: UIView.ContentMode
final class Coordinator: NSObject {}
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIView(context: Context) -> UIImageView {
let view = UIImageView()
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
view.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
view.image = image
view.contentMode = contentMode
return view
}
func updateUIView(_ uiView: UIImageView, context: Context) {
uiView.image = image
uiView.contentMode = contentMode
}
}
You can use it just like a normal SwiftUI's Image.
#ViewBuilder
var body: some View {
FixImageView(image: image, contentMode: .scaleAspectFit)
.cornerRadius(20)
}
If you're wondering why the the compression priority is low, Tomas Linhart explains:
SwiftUI uses the compression resistance priority and the content hugging priority to decide what resizing is possible.
If you want to resize a view below its intrinsic content size, you need to reduce the compression resistance priority.

Make NavigationLink destination view executed "onShow"

I am building a fitness app where user selects one of the programs from the list and when he clicks on selected program a new view appears with video playing (video has it's countdown timer on it).
I built the screen using NavigationView/NavigationLink each having it's "destination view" with it's own params.
let sets: [TrainingSet]
init() {
self.sets = [set1,set2,set3]
}
var body: some View {
NavigationView {
VStack {
ForEach(self.sets) { set in
NavigationLink(destination: ExerciseVideoView(items: set.items).navigationBarBackButtonHidden(true)) {
VStack {
Image("group-\(set.image)")
.resizable()
.renderingMode(.original)
.frame(height: 200, alignment: .leading)
.overlay(
Text(set.purpose)
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
)
Color.blue
.frame(maxWidth: .infinity)
.overlay(
Text(set.purpose)
.foregroundColor(.white)
)
}
}
}
}
}
}
1) I noticed that when the parent view is built, all destination views (ExerciseVideoView) get executed even before user clicks on corresponding button. My countdown timers start in background. I was supposed to see them launched when user click on NavigationLink and new view is "executed". Is that correct behaviour? Can I make destination view "executed/started" when they are shown?
2) The second problem is I wanna show the images with blue buttons below each of them (I put them both inside a VStack container). But when I launch my app only the images are shown but "Color.blue" rectangles are not visible. Why is that? How to make them visible as well?

Text has only one line when put in HStack with something else (SwiftUI List)

im trying to show a list of items. Every row should contain a stripe on the left in a specific color and a headline. This headline is sometimes more than a line long but only shows as one line and "...". When I remove the Stripe it shows as multiline text. I've attached the code and two pictures for comparison
Heres my code :
HStack {
Rectangle()
.foregroundColor(poll.outcome ? .green : .red)
.frame(width: 3)
VStack {
Text(poll.poll.title!).font(.headline)
.lineLimit(2)
}
}
This is how it looks without the Rectangle:
And with the Rectangle:
In a List or ScrollView the SwiftUI engine will compress back the text area. .lineLimit(x) give you a maximum of line, not a minimum ;)
To secure that the engine does not shrink back the Text height and goes up to the maximum limit, add .fixedSize(horizontal: false, vertical: true) as shown below
HStack {
Rectangle()
.foregroundColor(.red)
.frame(width: 3)
VStack {
Text("line1\nline2\nline3").font(.headline)
.lineLimit(2)
.multilineTextAlignment(.leading)
.fixedSize(horizontal: false, vertical: true)
}
}
SwiftUI has some bugs right now and it is one of them and there is some discussion on this answer that you can check out.
Although It works on my machine, if you have any trouble about sizing elements, you can use Spacers as a workaround until all SwiftUI bugs fix by Apple.
For this case, you can wrap your text between two spacers like this:
VStack {
Spacer()
Text("FirstLine\nSecondLine\nThirdLine")
.font(.headline)
.lineLimit(2)
Spacer()
}