When I implement a Button in SwiftUI this Button always has a standard animation when you click on it. A slight opacity animation. Is there a way to get rid of that animation?
Here is a simple example.
Here is a small video -> https://imgur.com/ClWd7YH
import SwiftUI
struct ContentView: View {
var body: some View {
VStack{
Button(action: {
// do somethinh
}) {
VStack{
ZStack{
Circle()
.fill(Color.white)
.frame(width: 45, height: 45, alignment: .center)
Image(systemName: "xmark.circle")
.foregroundColor(Color.black)
.font(Font.system(size: 18, weight: .thin))
.padding(6)
}
Text("Press")
.foregroundColor( Color.white)
}.frame(width: 300, height: 300, alignment: .center)
}
}.background(Color.green)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can define your own ButtonStyle which will override the default animation:
public struct ButtonWithoutAnimation: ButtonStyle {
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
}
}
Now just set the the style on the Button:
Button(action: { .. } {
..
}.buttonStyle(ButtonWithoutAnimation())
You can get what you want by creating your own buttonStyle. This way you have to specify an animation to use on tap. If you don't specify any animation the button won't have any animation. For example:
struct NoAnimationButton: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
}
}
struct ContentView: View {
var body: some View {
VStack{
Button(action: {
// do somethinh
}) {
VStack{
ZStack{
Circle()
.fill(Color.white)
.frame(width: 45, height: 45, alignment: .center)
Image(systemName: "xmark.circle")
.foregroundColor(Color.black)
.font(Font.system(size: 18, weight: .thin))
.padding(6)
}
Text("Press")
.foregroundColor( Color.white)
}
.frame(width: 300, height: 300, alignment: .center)
}
}
.background(Color.green)
.buttonStyle(NoAnimationButton())
}
}
Consider that you can always avoid using the Button view and implement the onTapGesture on your whole tappable view (or wherever you need it):
struct ContentView: View {
var body: some View {
VStack{
VStack{
ZStack{
Circle()
.fill(Color.white)
.frame(width: 45, height: 45, alignment: .center)
Image(systemName: "xmark.circle")
.foregroundColor(Color.black)
.font(Font.system(size: 18, weight: .thin))
.padding(6)
}
Text("Press")
.foregroundColor( Color.white)
}
.frame(width: 300, height: 300, alignment: .center)
}
.background(Color.green)
.onTapGesture {
//manage click
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
EDIT: Just to make an example of a buttonStyle with animation:
struct ScaleEffectButton: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.9 : 1)
}
}
Related
I'm trying to build a custom datepicker which is working perfectly so far. The last thing I try to increase is the tappable area, which I prefer to be the entire shape. Currently, a user has to tap the Calendar picture for date selection.
I've experimented with Contentshape, increasing the frame and adding padding, but nothing works as expected. How can I increase the tappable area while keeping it looking like this? Ideally, a user could tap the area within the border and the picker pops up.
My code:
struct DatePickerView: View {
#State private var selectedDate = Date()
var body: some View {
VStack(alignment: .leading) {
Text("Datum")
.foregroundColor(.black)
HStack {
Text("\(selectedDate.formatted())")
Spacer()
ZStack {
DatePicker("", selection: $selectedDate, in: ...Date(), displayedComponents: .date)
.datePickerStyle(.compact)
.labelsHidden()
.accentColor(.black)
SwiftUIWrapper {
Image(systemName: "calendar")
.resizable()
.frame(width: 18, height: 20)
}
.allowsHitTesting(false)
}
.frame(width: 18, height: 20)
}
.padding(16)
.overlay {
RoundedRectangle(cornerRadius: 8)
.strokeBorder(
style: StrokeStyle(
lineWidth: 0.5
)
)
.foregroundColor(.black)
}
}
}
}
struct DatePicker_Previews: PreviewProvider {
static var previews: some View {
DatePickerView()
}
}
struct SwiftUIWrapper<T: View>: UIViewControllerRepresentable {
let content: () -> T
func makeUIViewController(context: Context) -> UIHostingController<T> {
UIHostingController(rootView: content())
}
func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {}
}
Managed to make it work with some minor tweaks. Especially scaleEffect!
.compositingGroup()
.scaleEffect(x: 10, y: 1.5)
.clipped()
Endresult:
struct DatePickerView: View {
#State private var selectedDate = Date()
var body: some View {
VStack(alignment: .leading) {
Text("Datum")
.foregroundColor(.gray)
ZStack {
DatePicker("", selection: $selectedDate, in: ...Date(), displayedComponents: .date)
.datePickerStyle(.compact)
.labelsHidden()
.accentColor(.black)
.compositingGroup()
.scaleEffect(x: 10, y: 1.5)
.clipped()
SwiftUIWrapper {
HStack {
Text(selectedDate.formatted())
Spacer()
Image(systemName: "calendar")
.resizable()
.frame(width: 18, height: 20)
}
.padding(16)
.background(Color.white)
}
.padding(.vertical, 32)
.fixedSize(horizontal: false, vertical: true)
.allowsHitTesting(false)
}
.overlay (
RoundedRectangle(cornerRadius: 8)
.strokeBorder(
style: StrokeStyle(lineWidth: 0.5)
)
.foregroundColor(.black)
)
}
}
}
struct SwiftUIWrapper<T: View>: UIViewControllerRepresentable {
let content: () -> T
func makeUIViewController(context: Context) -> UIHostingController<T> {
UIHostingController(rootView: content())
}
func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {}
}
struct DatePicker_Previews: PreviewProvider {
static var previews: some View {
DatePickerView()
}
}
I'm encountering an issue with SwiftUI on macOS (12.x) where a scaled background is interfering with mouse clicks. The following is a minimal example:
struct ContentView: View {
var body: some View {
VStack {
Button("Test") {
print("Tested")
}
Image(systemName: "waveform.circle")
.resizable()
.frame(width: 200, height: 200)
.background(
Image(systemName: "waveform.circle")
.resizable()
.foregroundColor(.red)
.scaleEffect(2.0)
.blur(radius: 20)
.clipped()
)
}
.frame(width: 500, height: 500)
}
}
Note that pressing the "Test" button doesn't actually work -- no message to the console is printed.
I've found a workaround using NSHostingView, but it's pretty ugly -- I'd love to know if there's a pure-SwiftUI solution to 'clip' the background View so that not only is it's appearance cut off, but also it's ability to intercept clicks.
Here's a workaround (note that turning off the scale effect also solves the issue).
struct ContentViewWorkAround: View {
#State private var scaleEffectOn = true
#State private var workAround = false
#ViewBuilder private var imageBackground: some View {
switch workAround {
case false:
Image(systemName: "waveform.circle")
.resizable()
.foregroundColor(.red)
.scaleEffect(scaleEffectOn ? 2.0 : 1.0)
.blur(radius: 20)
.clipped()
.frame(width: 200, height: 200) // ineffective at preventing clicks
case true:
NSHostingViewRepresented {
Image(systemName: "waveform.circle")
.resizable()
.foregroundColor(.green)
.scaleEffect(2.0)
.blur(radius: 20)
.clipped()
.frame(width: 200, height: 200)
}
}
}
var body: some View {
VStack {
Button("Test") {
print("Tested")
}
Image(systemName: "waveform.circle")
.resizable()
.frame(width: 200, height: 200)
.background(
imageBackground
)
Toggle("Scale effect", isOn: $scaleEffectOn)
Toggle("Workaround", isOn: $workAround)
}
.frame(width: 500, height: 500)
}
}
struct NSHostingViewRepresented<V>: NSViewRepresentable where V: View {
var content: () -> V
func makeNSView(context: Context) -> NSHostingView<V> {
NSHostingView(rootView: content())
}
func updateNSView(_ nsView: NSHostingView<V>, context: Context) { }
}
Have you tried?
.allowsHitTesting(false)
Deactivates the clicks and allows whatever look you want.
struct ContentView: View {
var body: some View {
VStack {
Button("Test") {
print("Tested")
}
Image(systemName: "waveform.circle")
.resizable()
.frame(width: 200, height: 200)
.background(
Image(systemName: "waveform.circle")
.resizable()
.foregroundColor(.red)
.scaleEffect(2.0)
.blur(radius: 20)
.clipped()
.allowsHitTesting(false)
)
}
.frame(width: 500, height: 500)
}
}
I am experimenting with SwiftUI ToolbarItem in various positions in my view, such as in bottomBar and navigation. I am wondering if it is possible to center the ToolBarItem vertically in the view, as opposed to the top or bottom. When setting up the placement for ToolBarItem, I am not seeing a placement property for centering. Any idea how this ToolBarItem could be centered in the view?
Here is my code for the ToolBarItem, currently set to .bottomBar:
struct ContentView: View {
#State private var cityName = ""
var body: some View {
NavigationView {
VStack() {
//some content
}.navigationTitle("Weather")
.toolbar {
ToolbarItem(placement: .bottomBar) {
HStack {
TextField("Enter City Name", text: $cityName)
.frame(minWidth: 100, idealWidth: 150, maxWidth: 240, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
Spacer()
Button(action: {
// some action
}) {
HStack {
Image(systemName: "plus")
.font(.title)
}
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Try this approach for placing your toolbar elements in the middle of the ContentView.
It should look exactly like the .toolbar {...} you have, and it functions exactly as you expect.
The main difference with this approach is that
you can put the toolbar anywhere you like.
You can also use GeometryReader for very fine placement in your view.
struct ContentView: View {
#State private var cityName = ""
var toolbar: some View {
HStack {
Spacer()
TextField("Enter City Name", text: $cityName)
.frame(minWidth: 100, idealWidth: 110, maxWidth: 140, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
Spacer()
Button(action: {}) {
Image(systemName: "plus").font(.title)
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}
Spacer()
}
}
var body: some View {
NavigationView {
VStack {
Spacer()
toolbar
Spacer()
}.navigationTitle("Weather")
}
}
}
For a SwiftUI layout in a macOS app, I have three Rectangles as shown below:
The code to produce this layout is:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
HStack {
ZStack {
Rectangle()
.fill(Color.purple)
.frame(width: 20)
Text("1")
.font(.subheadline)
.foregroundColor(.white)
}
ZStack {
Rectangle()
.fill(Color.orange)
Text("2")
.font(.subheadline)
.foregroundColor(.white)
}
}
HStack {
ZStack {
Rectangle()
.fill(Color.red)
.frame(height: 20)
Text("3")
.font(.subheadline)
.foregroundColor(.white)
}
}
}
.frame(minWidth: 400, minHeight: 250)
}
}
My objective is for Rectangle 1 to be the same height as Rectangle 2 and for Rectangle 3 to be the same width as Rectangle 2. The size relationships between the rectangles should stay the same as the window size is changed. When done correctly, the final result should look like the following:
How can I accomplish this in SwiftUI?
Here is a working approach, based on view preferences. Tested with Xcode 11.4 / macOS 10.15.6
struct ViewWidthKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
struct ContentView: View {
#State private var boxWidth = CGFloat.zero
var body: some View {
VStack {
HStack {
ZStack {
Rectangle()
.fill(Color.purple)
.frame(width: 20)
Text("1")
.font(.subheadline)
.foregroundColor(.white)
}
ZStack {
Rectangle()
.fill(Color.orange)
Text("2")
.font(.subheadline)
.foregroundColor(.white)
}
.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width) })
}
HStack {
ZStack {
Rectangle()
.fill(Color.red)
.frame(height: 20)
Text("3")
.font(.subheadline)
.foregroundColor(.white)
}.frame(width: boxWidth)
}.frame(maxWidth: .infinity, alignment: .bottomTrailing)
}
.onPreferenceChange(ViewWidthKey.self) { self.boxWidth = $0 }
.frame(minWidth: 400, minHeight: 250)
}
}
I do have a few items that I need to hide ( not disable ) or show depending on a value.
As for a Text() or BUtton() sample, I need to have a overlay or no overlay.
Button("how secret?", action: {
self.secretOverlay = true
})
.overlay( TopSecretOverlayView()
....
})
I did try something like
struct TopSecretOverlayView: View {
var body: some View {
HStack {
if secretOverlay {
Text("Top Secret")
.bold()
.font(.system(size: 64))
.frame(width: 350, height: 80, alignment: .center)
} else {
....
}
}
}
}
.presentation is deprecated. Not sure if that was the way.
But how should I switch a overlay between hidden and visable?
Where should an if statement look like?
As always, thank you!
Have you tried this:
Button("how secret?") {
showSecretOverlay = true
}
.overlay(TopSecretOverlayView().opacity(showSecretOverlay ? 1 : 0))
Another way of handling it is with a view builder:
struct SecretView: View {
#State private var showSecretOverlay = false
var body: some View {
Button("show password?") {
showSecretOverlay.toggle()
}
.overlay(secretOverlay)
}
#ViewBuilder private var secretOverlay: some View {
if showSecretOverlay {
TopSecretOverlayView()
}
}
}
struct TopSecretOverlayView: View {
var body: some View {
Text("password")
.foregroundColor(.white)
.background(.black)
}
}
struct SecretView_Previews: PreviewProvider {
static var previews: some View {
SecretView()
}
}
If the button tap will always have the same action, then how about setting the opacity to 0 if you want it invisible and a 1 if you want it visible?
I know this may be a little old but it seems like you could probably do what you want using an ObservedObject passed into the view.
struct TopSecretOverlayView: View {
#ObservedObject var secretVM: SecretViewModel
var body: some View {
HStack {
switch secretVM.accessType {
case .top:
Text("Top Secret")
.bold()
.font(.system(size: 64))
.frame(width: 350, height: 80, alignment: .center)
case .middle:
Text("Secret")
.bold()
.font(.system(size: 64))
.frame(width: 350, height: 80, alignment: .center)
case .low:
Text("Common Knowledge")
.bold()
.font(.system(size: 64))
.frame(width: 350, height: 80, alignment: .center)
default:
Text("Access Denied")
.bold()
.font(.system(size: 64))
.frame(width: 350, height: 80, alignment: .center)
}
}
}
}
Then where ever you change the accessType property it should cause it to update the overlay.
Button("how secret?", action: {
self.secretVM.accessType = .top
})
.overlay( TopSecretOverlayView(secretVM:self.secretVM)
....
})