Changing button style from .bordered to .borderedProminent when pressed - ios

I'm trying to create a button that changes its appearance from .bordered to .borderedProminent to make some kind of illusion of a button that stays pressed when clicked.
Button {}
label: {Text("Xyz").frame(minWidth: 0, maxWidth: .infinity)}
.controlSize(.regular)
.buttonStyle(.bordered)
.buttonBorderShape(.automatic)
I tried using action function to do it but with no success. Is it even possible?

In order to achieve your use case, You should create custom button style.
struct CustomButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
. background(configuration.isPressed ? Color.green : Color.yellow)
}
}
& apply customButtonStyle to your button.
You can when button is pressed it will be green otherwise yellow. You can change this code to match requirement. Let me know if its useful.

Related

Present sheet with a TextField and its keyboard in a single animation?

I'm building a SwiftUI to-do app. You tap an Add button that pulls up a partial-height sheet where you can enter and save a new to-do. The Add sheet's input (TextField) should be focused when the sheet appears, so in order to keep things feeling fast and smooth, I'd like the sheet and the keyboard to animate onscreen together, at the same time. After much experimentation and Googling, I still can't figure out how to do it.
It seems like there are two paths to doing something like this:
(1) Autofocus the sheet
I can use #FocusState and .onAppear or .task inside the sheet to ensure the TextField is focused as soon as it comes up. It's straightforward functionally, but I can't find a permutation of it that will give me that single animation: it's sheet, then keyboard, presumably because those modifiers don't fire until the sheet is onscreen.
(2) Keyboard accessory view / toolbar
The .toolbar modifier seems tailor-made for a view of custom height that sticks to the keyboard--you lose the nice sheet animation but you gain the ability to have the view auto-size. However, .toolbar is designed to present controls alongside a TextField that itself isn't stuck to the keyboard. That is, the field has to be onscreen before the keyboard so it can receive focus...I don't know of a way to put the input itself inside the toolbar. Seems like chat apps have found a way to do this but I don't know what it is.
Any help would be much appreciated! Thanks!
Regarding option (1), I think there is no way to sync the animation. I decided to do it this way and don't worry about the delay between sheet and keyboard animation. Regarding option (2), you could try something like this:
struct ContentView: View {
#State var text = ""
#FocusState var isFocused: Bool
#FocusState var isFocusedInToolbar: Bool
var body: some View {
Button("Show Keyboard") {
isFocused = true
}
.opacity(isFocusedInToolbar ? 0 : 1)
TextField("Enter Text", text: $text) // Invisible Proxy TextField
.focused($isFocused)
.opacity(0)
.toolbar {
ToolbarItem(placement: .keyboard) {
HStack {
TextField("", text: $text) // Toolbar TextField
.textFieldStyle(.roundedBorder)
.focused($isFocusedInToolbar)
Button("Done") {
isFocused = false
isFocusedInToolbar = false
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
}
}
.onChange(of: isFocused) { newValue in
if newValue {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.05) {
isFocusedInToolbar = true
}
}
}
}
}
The trick is, that you need a TextField in your content that triggers the keyboard initally and then switch focus to the TextField in the toolbar. Otherwise you won't get the keyboard to show up.

How to add more padding below a TextView when the keyboard is shown and enable button clicks in SwiftUI

I wanna give my textfield some extra space while SwiftUI is automatically scrolling to it. As described in this question: How to add more padding below a TextField. With the answer from this one it works fine: https://developer.apple.com/forums/thread/699111 but cause I am manipulating the safe area of the view my button touches are not accepted anymore if they are outside of the safe area.
ScrollView {
VStack {
Spacer(minLength: 1000)
TextField("Textfield 1", text: $text)
.padding(30)
Button {
print("Button tapped")
} label: {
Text("Click this button")
}
Spacer(minLength: 1000)
}
}
.keyboardAvoiding()
This example code shows the problem. If I am selecting the TextField and the keyboard appears there's enough space below but If I am clicking at the button nothing happens. Any idea to work around this behaviour or any other way to add more padding below the TextField in SwiftUI?

Why do SwiftUI Buttons that use .buttonStyle see taps when a Menu is presented?

Goal Display a list of "card like" rows where a tap highlights the entire card and then performs an action. The rows need to support a Menu in their content.
The code below does this and at first seems to work fine. However, long pressing on a row while the menu is presented breaks the SwiftUI view hierarchy and requires the app be restarted to restore normal functionality.
struct ContentView: View {
let strings: [String] = ["Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot"]
#State var shouldDoSomething = false
var body: some View {
List(strings, id: \.self) { string in
HStack {
Text(string)
Spacer()
Menu {
Button {} label: { Label("Show Less", systemImage: "hand.thumbsdown") }
Button {} label: { Label("Show More", systemImage: "hand.thumbsup") }
} label: { Image(systemName: "ellipsis.circle") }
}
.padding()
// Make entire card tappable and highlight on tap
.background() {
Button(action: {
shouldDoSomething = true
}, label: { Color.white })
.buttonStyle(cardButtonStyle())
}
// Card style
.listRowSeparator(.hidden)
.overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.gray, lineWidth: 0.5))
.cornerRadius(12)
.shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 5)
}
.listStyle(.plain)
.alert(isPresented: $shouldDoSomething) {
Alert(title: Text("Row Tapped"), message: Text("Do something useful here."), dismissButton: .cancel())
}
}
}
struct cardButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.overlay {
configuration.isPressed ? Color.black.opacity(0.25) : Color.clear
}
}
}
The Problem
If the user taps the ellipses to bring up the Menu, then taps on any card. The Menu is dismissed and an Alert is displayed. The tap should not present the alert, it should only dismiss the Menu. This is an annoying tiger trap, but I could convince myself to live with it.
Worse, when the user taps the ellipses to bring up the Menu, then long presses on any card. Three things are different from the use case above.
The Alert does not appear (it shouldn't)
The Menu does not dismiss (it doesn't in normal cases either)
The console prints an error Attempt to present...which is already presenting... (Uh oh...)
The app is now broken, tap to dismiss the Menu and now the card rows are no longer tappable unless the app is restarted.
What I have tried to resolve the issue:
A. Dispatch the shouldDoSomething = true on the main queue, with and without a delay. This did not help.
B. I tried manually dismissing the Menu by declaring the dismiss environment variable and calling dismiss() when the button is pressed. This did not help.
C. Using .onAppear() and added tap and long press gestures, I tried to keep track of when the Menu was being displayed and using that information, disable the background button. Unfortunately, I was never able to detect the Menu is being displayed when triggered by a long press and when it is dismissed.
D. I tried a standard button in the row instead of a button in the view's background. This did not help.
E: Removed the four lines of "Card Style" code and .listStyle to see if they contributed to the problem. This did not help.
F. Through trial and error. I commented out the .buttonStyle(cardButtonStyle()) line and the problem went away. I switched the buttonStyle to an Apple standard one and the problem still persists. So .buttonStyle manifests the problem, why, is the question.

SwiftUI - Button action draws on an image

Hobbyist ios coder here...
I have an old Objective C project I am trying to recreate in swiftui.
When I click a button, it draws a CGRect and fills in the peramiters of image source from the IBAction I wrote out that i can then drag arround the screen.
Old way I used to do it
How do i do this in SwiftUI?
My empty SwiftUI Button Code
I currently have an image drawn within a VStack that I can move arround and resize, but I want it to not exist on app load, but for it and others to be drawn on user request. IE button press.
I have no idea what it is im supposed to search for to find this answer as searching for button and image instantly gives me tutorials on how to add images to a button, not a button that draws a fresh interactable element on the view.
Thank you to anyone who can shed more light on this.
EDIT - On pressing the button a second time I need the image to spawn again so there would be multiple instances of it.
I'm not 100% sure if I understand this correctly, but it's simply a matter of displaying an image when a button is clicked?
You already said that you got an image with the desired behavior, but you don't want it to be there from the beginning. I think you are looking for an alternative in SwiftUI for addSubview, but there is no direct one.
What you might be looking for is #State in SwiftUI. Changing a #State variable will force your view to redraw itself. (There are more then just #State variables that to this)
You can draw your Image based on a condition. On your button click you could change the state variable that your condition is based on to redraw your view and to display your Image. I use the toggle() function in my example, so clicking the Button a second time will result in removing the image.
import SwiftUI
struct ContentView: View {
#State private var showImage = false
var body: some View {
VStack {
if showImage {
Image(systemName: "gear") // Your working image
}
Button(action: {
self.showImage.toggle()
}, label: {
Text("draw image")
})
}
}
}
Here is an example how you could add more and more Images when clicking the button again and again. I changed the Type of the #State variable to an array and then iterate over that array via a ForEach.
import SwiftUI
struct ContentView: View {
#State private var images: [UUID] = []
var body: some View {
VStack {
ForEach(images, id: \.self) { _ in
Image(systemName: "gear") // Your working image
}
Button(action: {
self.images.append(UUID())
}, label: {
Text("draw image")
})
}
}
}

In SwiftUI, how can I perform a gesture but forward gestures to the view behind it?

I am creating a tooltip system.
I want to dismiss the tooltip if the user touches anywhere outside the tooltip.
I would like it so that a touch outside the tooltip both dismisses the tooltip and activates any controls the user tapped on. (So you could have a tooltip open and still click a button outside the tooltip and have it activate on the first tap.)
To do this, I have an invisible view handling the tap gesture and dismissing the tooltip, but I do not know how to make SwiftUI not intercept and cancel the tap gestures. On the web, it's the equivalent of not calling event.stopPropagation() and event.preventDefault(), or calling super in touchesBegan: in UIKit.
Any ideas?
Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4
struct ContentView: View {
var body: some View {
VStack {
Button("Button") { print("> button tapped")}
}
.frame(width: 200, height: 200)
.contentShape(Rectangle()) // makes all area tappable
.simultaneousGesture(TapGesture().onEnded({
print(">>> tooltip area here")
}))
.border(Color.red) // just for demo show area
}
}
You need to use this modifier:
.allowsHitTesting(false)

Resources