ProgressView vs View inside ZStack behave differently - swift

I have 2 views inside a ZStack. One is ProgressView() and another one is Circle().
In this ZStack, i have a green background color with onTapGesture event.
I notice that whenever i click on the ProgressView() the event of background color do not trigger but when i click on the Circle() it trigger the event of background color.
So why is that if both of these are on top of background color? i think it should not trigger the event for both views.
ZStack() {
Color.green.ignoresSafeArea(.all)
.onTapGesture {
print("checked")
}
VStack() {
ProgressView()
Circle().frame(width: 30, height: 30, alignment: .leading)
}
}

They are actually not. Let's consider view hierarchy below (from view debug mode)
Shape is a native SwiftUI view and it is just rendered into the same backend as background and, actually by default not hit-testable.
ProgressView in contrary has UIKit backend, because it just representable of UIView, and all UIViews are added above native SwiftUI view (note, even if they are put into background). And by default this opaque UIView does not pass touch events through to SwiftUI.
That's it.
*changed to yellow color for better visibility

As #Asperi wrote your VStack is in front of the Color View.
But one can use the modifier allowsHitTesting.
With this gesture are not processed by this view.
#State var text: String = "Hello World"
var body: some View {
ZStack() {
Color.green.ignoresSafeArea(.all)
.onTapGesture {
text = "Green tapped"
}
VStack() {
ProgressView()
Circle().frame(width: 30, height: 30, alignment: .leading)
Text(text)
}
.allowsHitTesting(false)
}
}

This seems like a normal behavior.
However, if you want to get rid of this problem, you can always wrap your stack with a simple blank onTapGesture{}.
VStack {
}.onTapGesture{}
onTapGesture{} will get your stack rid of this problem, plus it will not affect your sub view and buttons inside the stack. Your Button inside the stack will behave normally.

Related

Changing the spacing of the underline of a UIButton

How can I change the spacing between a UIButton and its underline? And also try to make the underline bolder. Any help is appreciated.
My UIButton:
enter image description here
The ideal UIButton:
enter image description here
To get such a button, you can split it int 2 elements - the text (not underlined) and a rectangle (as the line).
SwiftUI
In SwiftUI you can achieve your desired design with a Stack within the button with a text and a rectangle in it
Button(action: {
self.anyFancyFunction()
}) {
VStack(){
Text("Button")
Rectangle().frame(height: 5)
}
.foregroundColor(.black)
}
And it would look like:
In this example you can adjust the spacing by adding a (positive or negative) offset. You can add a corner radius to the bar and ich you wanted to have it rounded on the edges, you also can use Capsule() instead of Rectangle()
UPDATE:
UIKit
Regarding UIKit, I think a possible solution could be to create a view with 2 subviews (1. text, 2. bar underneath the text) and then you put a clear button with the same size over it on the same position.
I'll try to add an UIKit example in the afternoon.
Best,
Sebastian
In SwiftUI, there is a View called Rectangle that is perfectly matched for this. You can add it below any view by embedding them into a simple VStack.
Here is the code:
import SwiftUI
struct ContentView: View {
var body: some View {
Button(action: {
}) {
VStack(spacing: 15){
Text("帖子")
.font(.system(size: 30))
Rectangle()
}
.frame(width: 70, height: 60, alignment: .center)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is the result:

Why does SwiftUI View background extend into safe area?

Here's a view that navigates to a 2nd view:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
SecondView()
} label: {
Text("Go to 2nd view")
}
}
}
}
struct SecondView: View {
var body: some View {
ZStack {
Color.red
VStack {
Text("This is a test")
.background(.green)
//Spacer() // <--If you add this, it pushes the Text to the top, but its background no longer respects the safe area--why is this?
}
}
}
}
On the 2nd screen, the green background of the Text view only extends to its border, which makes sense because the Text is a pull-in view:
Now, if you add Spacer() after the Text(), the Text view pushes to the top of the VStack, which makes sense, but why does its green background suddenly push into the safe area?
In the Apple documentation here, it says:
By default, SwiftUI sizes and positions views to avoid system defined
safe areas to ensure that system content or the edges of the device
won’t obstruct your views. If your design calls for the background to
extend to the screen edges, use the ignoresSafeArea(_:edges:) modifier
to override the default.
However, in this case, I'm not using ignoresSafeArea, so why is it acting as if I did, rather than perform the default like Apple says, which is to avoid the safe areas?
You think that you use old background modifier version.
But actually, the above code uses a new one, introduced in iOS 15 version of background modifier which by default ignores all safe area edges:
func background<S>(_ style: S, ignoresSafeAreaEdges edges: Edge.Set = .all) -> some View where S : ShapeStyle
To fix the issue just replace .background(.green) with .background(Color.green) if your app deployment target < iOS 15.0, otherwise use a new modifier: .background { Color.green }.

Moving view with DragGesture while having a ScrollView on top

I have a view that can be dragged by the user. This view contains a ScrollView that shows a list of information. However, this view interferes with the DragGesture of the containing view.
The expected behavior would be that only when the user can actually scroll something, the gesture of the ScrollView would have priority over the container.
Here is the code that can be used to test the problem I'm referring to:
struct MultipleDrag: View {
#GestureState private var offset: CGFloat = 0
private var gesture: some Gesture {
DragGesture()
.updating($offset) { value, state, transaction in
state = -value.translation.height
}
}
var body: some View {
VStack {
ScrollView {
ForEach(1...20, id: \.self) { val in
Text(String(val))
.padding()
}
}
.background(Color.green)
.cornerRadius(10)
.padding()
}
.frame(height: 200)
.background(Color.orange)
.cornerRadius(10)
.offset(y: -offset)
.gesture(gesture)
}
}
As you can see, by default the scrollview starts at the top. That would mean that if the user swiped down, the orange container should move downwards, even if the gesture took place in the green area (ScrollView). The same should happen when the bottom of the ScrollView was reached or when there's not enough content on the scrollview to actually scroll.
Any ideas?
I think you can use this modifier:
.highPriorityGesture(Gesture)
This will make the gesture with a higher priority than other gestures.

What is this SwiftUI view called?

What is this SwiftUI popup View called (The one that says that I have earned 30 Bits)?
In terms of a standard UI component that causes a view like that to pop up, there is none.
However, you can create one yourself.
The basic frame is most likely a Capsule, which you can create by doing this:
VStack {
Text("My info")
}
.frame(width: 300, height: 50)
.background(
Capsule()
.fill(Color.white)
)
You can put that on top of the rest of your view hierarchy using a ZStack

How can I get a SwiftUI View to completely fill its superview?

The following is supposed to create a Text whose bounds occupy the entire screen, but it seems to do nothing.
struct ContentView: View {
var body: some View {
Text("foo")
.relativeSize(width: 1.0, height: 1.0)
.background(Color.red)
}
}
The following hack:
extension View {
/// Causes the view to fill into its superview.
public func _fill(alignment: Alignment = .center) -> some View {
GeometryReader { geometry in
return self.frame(
width: geometry.size.width,
height: geometry.size.height,
alignment: alignment
)
}
}
}
struct ContentView2: View {
var body: some View {
Text("foo")
._fill()
.background(Color.red)
}
}
seems to work however.
Is this a SwiftUI bug with relativeSize, or am I missing something?
You need to watch WWDC 2019 Session 237: Building Custom Views with SwiftUI, because Dave Abrahams discusses this topic, and uses Text in his examples.
To restate briefly what Dave explains in detail:
The parent (in this case, a root view created by the system and filling the screen) proposes a size to its child.
The child chooses its own size, consuming as much or as little of the proposed size as it wants.
The parent positions the child in the parent’s coordinate space based on various parameters including the size chosen by the child.
Thus you cannot force a small Text to fill the screen, because in step 2, the Text will refuse to consume more space than needed to fit its content.
Color.red is different: in step 2, it just returns the proposed size as its own size. We can call views like this “expandable”: they expand to fill whatever space they're offered.
ZStack is also different: in step 2, it asks its children for their sizes and picks its own size based on its children's sizes. We can call views like this “wrapping”: they wrap their children tightly.
So if you promote Color.red to be the “main” view returned by body, and put the Text in an overlay, your ContentView will behave like Color.red and be expandable:
struct ContentView: View {
var body: some View {
Color.red
.overlay(Text("foo"))
}
}
If you use a ZStack containing both Color.red and Text, the ZStack will wrap the Color.red, and thus take on its expandability:
struct ContentView: View {
var body: some View {
ZStack {
Color.red
Text("hello")
}
}
}