I try to add full screen transparent button:
Button(action: {
// my action
}) {
Rectangle()
.opacity(0)
}
But in case .opacity() is less than 0.1 button action stop working. How to implement full screen transparent button?
Tested on iOS 14.3(Sim), iOS 14.2(iPhone X), Xcode 12.3
Here is possible solution. Tested with Xcode 12.1 / iOS 14.1
struct DemoClearButton: View {
var body: some View {
Color.clear
.contentShape(Rectangle())
.onTapGesture {
print(">> transparent tapped")
}
}
}
Note: probably in place of usage you'd wanted to add .edgesIgnoringSafeArea(.all)
For me the .contentShape didn't work. At least, it didn't prevent the view behind my button to also receive the touch.
But, and I feel dirty for suggesting this:
Button(action: {
print("Yeahhh")
}) {
Text("Yeah?").padding(64)
}
.background(LinearGradient(colors: [Color.clear], startPoint: .top, endPoint: .bottom))
Adding a transparent LinearGradient did the trick for me.
Related
I am trying to make the SwiftUI Form translucent. I've tried applying .background(.thinMaterial) modifier to the Form. However, this changes the look of the scroll view background. I'd like to apply translucency to the area that is white in the picture I attached.
Is there a way to do it? I am developing for iOS 16.
var body: some View {
NavigationStack(path: $path) {
ZStack {
LinearGradient(gradient: Gradient(colors: [.pink, .yellow]),
startPoint: .topTrailing,
endPoint: .bottomLeading)
.edgesIgnoringSafeArea(.all)
Form {
VStack {
...
}
}.scrollContentBackground(.hidden)
}
}
It seems Form is just a List under the hood. So by applying .scrollContentBackground(.hidden) you are clearing the List background.
By using .background on Form you are setting the background for the entire List again. To set the background only on the implicit Section you need another modifier. .listRowBackground.
But listRowBackground has this signature:
func listRowBackground<V>(_ view: V?) -> some View where V : View
So you canĀ“t use .thinMaterial. But you can add a new background to the VStack.
A possible solution would be:
var body: some View {
NavigationStack(path: $path) {
ZStack {
LinearGradient(gradient: Gradient(colors: [.pink, .yellow]),
startPoint: .topTrailing,
endPoint: .bottomLeading)
.edgesIgnoringSafeArea(.all)
Form {
VStack {
TextField("", text: $text)
Button("test"){
}
.buttonStyle(.borderedProminent)
Button("test"){
}.buttonStyle(.borderedProminent)
}
// this will clear the background
.listRowBackground(Color.clear)
// add some padding around the VStack
.padding()
// apply a new background
.background(.ultraThinMaterial)
// make the edges round again
.mask {
RoundedRectangle(cornerRadius: 20)
}
}
.scrollContentBackground(.hidden)
}
}
}
Result:
Change the form's opacity to bleed through the background colour/image:
Form {
/...
}
.opacity(0.5). // 0 = fully translucent ... 1 = opaque
I created SwiftUI Button and its touch area is slightly strange. The touch area extends the label of the button.
This is my code.
struct TestView: View {
var body: some View {
HStack(spacing: 0) {
Spacer()
Button {
print("aaaaaa")
} label: {
HStack(spacing: 0) {
Spacer()
}
.frame(width: 50.0, height: 50.0)
.background(Color.yellow)
}
Spacer()
}
.frame(height: 50.0)
.background(Color.red)
}
}
And I'll attach the result in simulator.
The button area is filled with yellow color. But I could click the button outside of yellow color.
The same thing happens in real device too.
How is this possible?
This is a completely normal behavior of Button/onTapGesture in SwiftUI. Its touch area is slightly bigger than its border size.
This is not a bug or glitch.
In SwiftUI, I have a Button that's underneath the Spacer within a ScrollView. The ScrollView steals the tap gesture, so the Button never sees the tap.
So, for example, the button in this example does not work:
struct DoesNotWork: View {
var body: some View {
ZStack {
VStack {
// Button doesn't work
Button("Tap This") {
print("Tapped")
}
Spacer()
}
VStack {
ScrollView {
Spacer()
.frame(height: 150)
Rectangle()
.foregroundColor(.blue)
.frame(height: 150)
Spacer()
}
}
}
}
}
This version -- with everything else the same except no ScrollView -- works fine:
struct ThisWorks: View {
var body: some View {
ZStack {
VStack {
// Button works normally
Button("Tap This") {
print("Tapped")
}
Spacer()
}
VStack {
Spacer()
.frame(height: 150)
Rectangle()
.foregroundColor(.blue)
.frame(height: 150)
Spacer()
}
}
}
}
So that rules out the VStack, ZStack, and the Spacer.
I've tried using .allowsHitTesting(false) on the Spacer that's within the ScrollView, and also tried .disabled(true) (and in combination). Adding .allowsHitTesting(false) to the ScrollView makes the button works but of course breaks the ScrollView.
I also tried setting .foregroundColor(.clear) on the Spacer.
For what it's worth, I'm having the same behavior with both Xcode 13.0 Beta 5 testing with iOS 15.0 beta 7, as well as with Xcode 12.5.1 with iOS 14.7.1.
I'm out of ideas. It seems like it should be the simplest thing in the world, but I can't figure out a way around this.
Any help is greatly appreciated!
I have a screen with two List side by side, inside a NavigationView. The layout is rendered correctly, and I can independently scroll both lists. The problem is that, when I scroll the first list, it goes behind the navigation bar without triggering the effect of applying a background color to it. Here's a gif showing what's going on:
And this is the code I'm using for this view:
struct ContentView: View {
var body: some View {
NavigationView {
HStack(spacing: 0) {
List {
Section(header: Text("Header left")) {
ForEach(0..<600) { integer in
Text("\(integer)")
}
}
}
.listStyle(InsetGroupedListStyle())
.frame(minWidth: 0, maxWidth: .infinity)
List {
Section(header: Text("Header right")) {
ForEach(0..<400) { integer in
Text("\(integer)")
}
}
}
.listStyle(InsetGroupedListStyle())
.frame(minWidth: 0, maxWidth: .infinity)
}
.navigationTitle("Example")
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
Would this be a SwiftUI bug? If not, how can I make the first list correctly interact with the navigation bar when scrolling?
It's a bug. The way the NavigationView and NavigationStack work, they only consider the last occurring List or ScrollView in its content to animate the navigation bar.
The only workarounds I can think of now are to:
Hide the navigation bar with
.navigationBarHidden(true)
or
.toolbar(.hidden) (for iOS 16+) modifiers
Fix the navigation bar in place
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(Color.white, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
for iOS 16+ or the UIKit-ish way for older iOS versions by playing with UINavigationBarAppearance
Implement your own navigation bar, track the lists with geometry readers, and do the animation based on the list positions but the effort here will not be worth it.
I've ended up with disabling UINavigationBar's large title and transparency.
import Introspect
#State private var navigationBar: UINavigationBar?
HStack(spacing: 0) {
...
}
.navigationBarTitleDisplayMode(.inline)
.introspectNavigationController {
$0.navigationBar.scrollEdgeAppearance = $0.navigationBar.standardAppearance
navigationBar = $0.navigationBar
}
.onDisappear {
navigationBar?.scrollEdgeAppearance = nil
navigationBar = nil
}
To access UINavigationController I used SwiftUI Introspect lib
I'm running into some weird behavior, trying to get a simple modal to pop after it has been dismissed.
I have an Add button in the NavigationBar that pops a modal. The modal has a button that will dismiss it, which works. However, I cannot interact with the Add button in the NavigationBar again until I interact with something else on the screen, such as scrolling the List below.
I have also placed another Add button, just for kicks, in the List itself, which always works.
Here's the code for the main view:
import SwiftUI
struct ContentView: View {
#State var displayModal: Bool = false
var body: some View {
NavigationView {
List {
Text("Hello again.")
Button(action: { self.displayModal = true }) {
Text("Add")
}
}
.sheet(isPresented: $displayModal) {
Modal(isPresented: self.$displayModal)
}
.navigationBarTitle("The Title")
.navigationBarItems(trailing: Button(action: { self.displayModal = true }) {
Text("Add")
})
}
}
}
And the modal, for completeness:
import SwiftUI
struct Modal: View {
#Binding var isPresented: Bool
var body: some View {
VStack {
HStack {
Button(action: {
self.isPresented = false
}) {
Text("Cancel")
}
.padding()
Spacer()
}
Text("I am the modal")
Spacer()
}
}
}
The only thing I can think of is that something invisible is preventing me from working with the NavigationBar button. So I fired up the UI Debugger, and here's what the ContentView looks like. Note the NavigationBar button.
Now, after I tap the button and display the modal, and then use the UI Debugger to see the ContentView again, all the same elements are in place, but the Button parent views are offset a bit, like this:
Once I drag the List up and down, the UI Debugger shows a view hierarchy identical to the first image.
Does anyone have any idea what's going on here?
I'm using Xcode 11.2.1 and iOS 13 on an iPhone 11 Pro simulator, but have also observed this on my iPhone.
It is really a bug. The interesting thing is that after 'drag to dismiss' the issue is not observed, so it is a kind of 'sync/async' state changing or something.
Workaround (temporary of course, decreases visibility almost completely)
.navigationBarItems(trailing: Button(action: { self.displayModal = true }) {
Text("Add").padding([.leading, .vertical], 4)
})
I ran into the same issue, and for me the workaround was to use an inline-style navigation bar title on the presenter.
.navigationBarTitle(Text("The Title"), displayMode: .inline)
HOWEVER, if you use a custom accent color on your ContentView (like .accentColor(Color.green)), this workaround no longer works.
Edit: the bug seems fixed in 13.4, and no workarounds are needed anymore.