SwiftUI Button touch area is abnormal - ios

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.

Related

Make all background UI blur after a view pop up

I plan to have a pop up view that blur out all the underlying UI behind it after it show, however my background "RoundedRectangle(cornerRadius: 20)
.background(
Color.white.opacity(0.7)
)" did not show a transparent white or blur like I planned.
var body: some View {
ZStack() {
NavigationStack {
ZStack() {
MyBackGround()
VStack() {
TextField("username", text: $user)
.padding()
.background(.brown)
SecureField("password", text: $pw)
.padding()
.background(.brown)
Button("CONFIRM") {
popUp.toggle()
}
.padding()
.cornerRadius(20)
.padding()
}
}
}
if popUp {
RoundedRectangle(cornerRadius: 20)
.background(
Color.white.opacity(0.7)
)
MyPopUP()
}
}
}
Your popup background is black because by default RoundedRectangle is filled with black color, so instead of adding background modifier we need to fill it with that color.
NavigationStack {
// content ...
}
.blur(radius: popUp ? 5 : 0) // to blur (constant is up to you)
.disabled(popUp) // bluring does not prevent interaction
if popUp {
// if semi-transparent background is still needed
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.white.opacity(0.7)) // << here !!
MyPopUP()
}
I suggest using the provided SwiftUI .blur() for this context instead.
Add this line of code after your NavigationStack.
NavigationStack{...}
.blur(radius: popUp ? 10 : 0)
if popUp {
MyPopUP()
}

Button style label image and text foreground color change at a different time?

I have a question about a part of my code in my custom button style. So I created a custom button style and this is what I am returning:
struct CustomStyle: ButtonStyle{
func makeBody(configuration: Configuration) -> some View {
return AnyView(Label {
configuration.label
} icon: {
Image(systemName: "plus").resizable().frame(width: 30, height: 30)
})
.foregroundColor(
configuration.isPressed ? Color.blue : Color.red
)
.padding()
.frame(maxWidth: .infinity, minHeight: 20)
.background(Color.yellow)
.clipShape(Capsule())
}
}
struct ContentView: View {
var body: some View {
VStack {
Button(action: {}, label: { Text("amin") })
.buttonStyle(CustomStyle())
}
}
}
The foregroundColor should change when I tap the button, and it does change. The issue is, the icon takes a few more milliseconds to go back to its original color. For example, let's say the color of the text and icon is red. When I click on the button both become blue, but text goes back to red immediately once I let go and icon(image) goes back to red with a very brief(a few millisecond) animation. I want both to be synced.
notes:
I know that most of the time in button styles we just return configuration.label, but what I am returning also works and has no issues. I have to do this because I want my button to have an image next to its text.
icon in this case is Image(systemName: "plus")
It is possible to fix by just disabling animation (tested with Xcode 13.4 / iOS 15.5)
struct CustomStyle: ButtonStyle{
func makeBody(configuration: Configuration) -> some View {
Label {
configuration.label
} icon: {
Image(systemName: "plus").resizable().frame(width: 30, height: 30)
}
.animation(.none, value: configuration.isPressed) // << here !!
.foregroundColor(
configuration.isPressed ? Color.blue : Color.red
)

SwiftUI iOS 14/15 Can't tap Button underneath Spacer in ScrollView

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!

In SwiftUI, how do I animate a view from its current position to the center of the screen?

In this sample app, I have a title in the top left of the screen, and a button in the bottom right. I'm using stacks and spacers to align them.
Currently, when you press the button, it animates up/left a little. But I want the button to animate to the exact center of the screen (or safe area), regardless of device or button size. The code is shown below, along with images of the start and end of the animation I want.
struct ContentView: View {
#State var buttonIsMoved = false
var body: some View {
VStack {
HStack {
Text("Title")
.font(.largeTitle)
Spacer()
}
Spacer()
HStack {
Spacer()
// This is the button I want to animate to the center
Button(action: {
self.buttonIsMoved.toggle()
}) {
Text("This is a button")
.foregroundColor(.black)
.padding(16)
.background(Color.green)
}
// Currently I'm just using fixed offset values,
// but I want it to move to the actual center of the screen
.offset(buttonIsMoved ? CGSize(width: -50, height: -200) : .zero)
.animation(.easeInOut)
}
}
.padding(32)
}
}
Start of animation
End of animation
If I use .offset(), I don't know how to calculate the distance between the button's center and the center of the screen. I've also tried to use .position() but it's based on the parent view, which in this case is an HStack below the title, so it wouldn't be centered within the whole screen. I've also heard of GeometryReader, but I can't figure out how to use it for this purpose.
Here is possible solution - no hardcoding, based on SwiftUI native layout engine.
Tested with Xcode 11.4 / iOS 13.4
struct DemoAnimateLayout: View {
#State var buttonIsMoved = false
var body: some View {
ZStack {
VStack {
HStack {
Text("Title")
.font(.largeTitle)
Spacer()
}
Spacer()
}
VStack {
if !buttonIsMoved { // << here !!
Spacer()
}
HStack {
if !buttonIsMoved { // << here !!
Spacer()
}
// This is the button I want to animate to the center
Button(action: {
self.buttonIsMoved.toggle()
}) {
Text("This is a button")
.foregroundColor(.black)
.padding(16)
.background(Color.green)
}
}
}
.animation(.easeInOut) // << animate container layout !!
}.padding(32)
}
}

Prevent SwiftUI System Image from dynamically changing size for navigation bar item

I would like to add a plus button to the navigation bar items using the system plus image in SwiftUI. However, I am unable to prevent the system image from scaling dynamically when the accessibility font changes.
How can I stop it resizing so it acts like a standard UINavigationBarButtonItem system plus button?
The accessibility feature of holding on to a navigation bar button for large font types also doesn't work like it does with UIKit.
Really frustrating that a potentially simple thing can't be done with SwiftUI and that accessibility hasn't been thought about. The SwiftUI tutorials profile bar button also doesn't work for large font sizes. (PS SwiftUI is the future)
Here was my attempt:
struct ContentView : View {
var body: some View {
NavigationView {
List {
Text("Stop + Bar Button resizing")
.lineLimit(nil)
}
.navigationBarTitle(Text("Plus"))
.navigationBarItems(trailing:
PlusNavigationButton()
)
}
}
}
struct PlusNavigationButton: View {
var body: some View {
PresentationButton(
Image(systemName: "plus")
.resizable()
.frame(width: 44, height: 44),
destination: NewView())
}
}
You should build what you exactly want. So if you want to use 16x16 image with some extra hitTest area, you can build like this:
var body: some View {
PresentationButton(
HStack() {
Spacer()
Image(systemName: "plus")
.resizable()
.frame(width: 16, height: 16)
Spacer()
}.frame(width: 44, height: 44),
destination: NewView()
)
}
or if you like some space around your image and let it fill the rest, you can:
var body: some View {
PresentationButton(
Image(systemName: "plus")
.resizable()
.padding(14)
.frame(width: 44, height: 44),
destination: NewView()
)
}

Resources