Calling 2 .toggle methods Swift UI - button

How would I go about calling 2 toggles on one button. One pushes the new view and one hides a view on the main view. `
Button(action: {
self.isPresentedPost.toggle()
self.showBottomSheet.toggle()
}, label: {
Image(systemName: "highlighter")
.font(.system(size:44, weight: .semibold))
.frame(width: 100, height: 100, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.foregroundColor(.white)
.background(Color(ecGray))
.cornerRadius(16)
.padding(2)
})
.fullScreenCover(isPresented: $isPresentedPost) {
UploadPostView()
}
Text("Post")
.font(.system(size:12, weight: .semibold))
.foregroundColor(.gray)
}`

Related

SwiftUI - How to perform action only while the button is tapped, and end it when the tap is released?

I'm trying to recreate a Game Boy like game pad in SwiftUI. Graphically it looks good, but I can't make the actions work. I would like it to perform the action (move in the selected direction) while the arrow is tapped, and to stop moving once the arrow isn't tapped anymore (just like a real game pad would). The code I tried so far is this one:
import SwiftUI
struct GamePad: View {
#State var direction = "Empty"
#State var animate = false
var body: some View {
ZStack {
VStack {
Text("\(direction) + \(String(describing: animate))")
.padding()
Spacer()
}
VStack(spacing: 0) {
Rectangle()
.frame(width: 35, height: 60)
.foregroundColor(.gray.opacity(0.3))
.overlay(
Button {
direction = "Up"
animate = true
} label: {
VStack {
Image(systemName: "arrowtriangle.up.fill")
.foregroundColor(.black.opacity(0.4))
Spacer()
}
.padding(.top, 10)
.gesture(
TapGesture()
.onEnded({ () in
direction = "Ended"
animate = false
})
)
}
)
Rectangle()
.frame(width: 35, height: 60)
.foregroundColor(.gray.opacity(0.3))
.overlay(
Button {
direction = "Down"
animate = true
} label: {
VStack {
Spacer()
Image(systemName: "arrowtriangle.down.fill")
.foregroundColor(.black.opacity(0.4))
}
.padding(.bottom, 10)
.gesture(
TapGesture()
.onEnded({ () in
direction = "Ended"
animate = false
})
)
}
)
}
HStack(spacing: 35) {
Rectangle()
.frame(width: 43, height: 35)
.foregroundColor(.gray.opacity(0.3))
.overlay(
Button {
direction = "Left"
animate = true
} label: {
VStack {
Image(systemName: "arrowtriangle.left.fill")
.foregroundColor(.black.opacity(0.4))
Spacer()
}
.padding(.top, 10)
.gesture(
TapGesture()
.onEnded({ () in
direction = "Ended"
animate = false
})
)
}
)
Rectangle()
.frame(width: 43, height: 35)
.foregroundColor(.gray.opacity(0.3))
.overlay(
Button {
direction = "Right"
animate = true
} label: {
VStack {
Spacer()
Image(systemName: "arrowtriangle.right.fill")
.foregroundColor(.black.opacity(0.4))
}
.padding(.bottom, 10)
.gesture(
TapGesture()
.onEnded({ () in
direction = "Ended"
animate = false
})
)
}
)
}
}
}
}
What am I doing wrong? Thanks
It can be achieved with custom button style, because it has isPressed state in configuration.
Here is a demo of possible solution. Tested with Xcode 13.4 / iOS 15.5
struct StateButtonStyle: ButtonStyle {
var onStateChanged: (Bool) -> Void
func makeBody(configuration: Configuration) -> some View {
configuration.label
.opacity(configuration.isPressed ? 0.5 : 1) // << press effect
.onChange(of: configuration.isPressed) {
onStateChanged($0) // << report if pressed externally
}
}
}
and updated button with it
Button {
direction = "Ended" // action on touchUP
} label: {
VStack {
Image(systemName: "arrowtriangle.up.fill")
.foregroundColor(.black.opacity(0.4))
Spacer()
}
.padding(.top, 10)
}
.buttonStyle(StateButtonStyle { // << press state is here !!
animate = $0
if $0 {
direction = "Up"
}
})
Test module on GitHub

SwiftUI button - only right side tappable

I have the following SwiftUI view which is essentially a textfield containing a button:
var body: some View {
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: Constants.cornerRadius, style: .continuous)
.stroke(isFocused ? Color.blue : hasWarning ? .blue : .gray, lineWidth: Constants.lineWidth)
HStack {
Text(title)
.font(.bodyFont)
.foregroundColor(isFocused ? Color.blue : hasWarning ? .red : .gray)
.padding(.horizontal, text.isEmpty ? Constants.Padding.horizontalIsEmpty : Constants.Padding.horizontalIsNotEmpty)
.background(text.isEmpty ? Color.clear : background)
.padding(.leading, Constants.Padding.leading)
.offset(y: text.isEmpty ? Constants.Offset.isEmpty : -(frameHeight / Constants.Offset.isNotEmptyRatio))
.scaleEffect(text.isEmpty ? Constants.ScaleEffect.isEmpty : Constants.ScaleEffect.isNotEmpty, anchor: .leading)
if let button = button {
Spacer()
Button(action: {
button.action()
}, label: {
Text(button.labelText)
})
.padding(.trailing, Constants.Button.trailingPadding)
}
}
TextField("", text: $text, onEditingChanged: { inFocus in
self.isFocused = inFocus
})
.font(.bodyFont)
.padding(.horizontal, Constants.Padding.horizontal)
}
.frame(height: frameHeight)
.background(background)
.animation(.easeInOut(duration: Constants.Animation.easeInOut))
.padding(.top, Constants.Padding.top)
}
The result is a field like this:
However, only the very right hand side of the button is tappable.
I have tried adding a contentShape, like this:
if let button = button {
Spacer()
Button(action: {
button.action()
}, label: {
Text(button.labelText)
})
.contentShape(Rectange())
.padding(.trailing, Constants.Button.trailingPadding)
}
However this has made no difference. Wondering where I'm going wrong here.
Found the issue! It was a simple mistake.
The HStack including the button needed to be wrapped around this element:
TextField("", text: $text, onEditingChanged: { inFocus in
self.isFocused = inFocus
})
.font(.bodyFont)
.padding(.horizontal, Constants.Padding.horizontal)
Previously what was happening was that this element was overlapping the button, obscuring the tappable area. So the correct code would be:
HStack {
TextField("", text: $text, onEditingChanged: { inFocus in
self.isFocused = inFocus
})
.font(.bodyFont)
.padding(.horizontal, Constants.Padding.horizontal)
if let button = button {
Spacer()
Button(action: {
button.action()
}, label: {
Text(button.labelText)
})
.padding(.trailing, Constants.Button.trailingPadding)
}
}

Tappable Image in a ZStack with a Button

I have a ZStack with an image and a close button overlaying it. I cannot make the image tappable with the button overlaying it.
ZStack(alignment: .topTrailing) {
NetworkImage(url: article.imageURL)
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height * 0.5)
.scaleEffect(scrollViewContentOffset < 0 ?
1 + (-scrollViewContentOffset * 0.005)
: 1,
anchor: .bottom)
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}){
Image(systemName: "xmark.circle.fill")
.foregroundColor(.init(white: 0.9)).padding([.top, .trailing])
.font(.system(size: topCloseButtonSize))
.offset(y: scrollViewContentOffset < 0 ?
.extraLarge + scrollViewContentOffset :
.extraLarge)
}
}
I have tried to add a tap gesture to the Image itself but that disables the tapability on the close button. How can I keep the ZStack in tact and have both the image and close button tappable?
Try adding an onTapGesture modifier to the NetworkImage (Rectangle in the code beloew) - it seems to work as expected then. I simplified the NetworkImage to use a standard Rectangle, but the tap results should be the same. Tested on Xcode 12.1.
ZStack(alignment: .topTrailing) {
Rectangle() // Use NetworkImage here - simplified to Rectangle for testing
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height * 0.5)
.onTapGesture(count: 1, perform: {
print("RECTANGLE TAPPED!")
})
Button(action: {
print("CLOSE TAPPED")
}){
Image(systemName: "xmark.circle.fill")
.foregroundColor(.init(white: 0.9)).padding([.top, .trailing])
}
}

Why does my Button style object only work on the text within the button and not the entire button?

I have below a ButtonStyle object that I wish to use when a Button is tapped. However, when I call it on my Button, the effect is seen only on the text within the button and not on the entire Button as a whole.
I have tried calling it outside but to no avail. Can someone please explain to me how I can fix my error?
Button(action: {
}) {
Text("Button")
.foregroundColor(.white)
}.background(Color.red)
.buttonStyle(ScaleButtonStyle())
struct ScaleButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 5 : 1)
}
}
EDIT:
Button(action: {}) {
Text("Button")
.fontWeight(.semibold)
.foregroundColor(.white)
.font(.title)
}
.buttonStyle(ScaleButtonStyle(bgColor: Color.red))
.frame(width: geo.size.width*0.8, height: geo.size.height*0.1, alignment: .center)
.background(Color.red)
.clipShape(Rectangle())
struct ScaleButtonStyle: ButtonStyle {
let bgColor: Color
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.background(bgColor)
.scaleEffect(configuration.isPressed ? 5 : 1)
}
}
The solution is to make background as part of a button style as shown below.
Tested with Xcode 11.4 / iOS 13.4
struct ScaleButtonStyle: ButtonStyle {
let bgColor: Color
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.background(bgColor)
.scaleEffect(configuration.isPressed ? 5 : 1)
}
}
struct TestScaleButtonStyle: View {
var body: some View {
Button(action: { }) {
Text("Button")
.foregroundColor(.white)
}.buttonStyle(ScaleButtonStyle(bgColor: Color.red))
}
}

SwiftUI: How to Navigate from one view to another by clicking on a button

I am trying to move from one view to another, I have used NavigationLink as following:
#State private var isActive = false
NavigationLink(destination: DetailsView(),
isActive: $isActive) {
Text("")}
Button(action: {
print ("Clicked")
self.isActive = true
}){
Text("More Details")
.font(.headline)
.bold()
.padding(.all, 10)
.padding(.leading, 10)
.padding(.trailing, 10)
.foregroundColor(.white)
.background(Color.orange.frame(width: 300, height: 50) .clipShape(RoundedRectangle(cornerRadius: 20)))
}
Unfortunately did not work!! The button did not take me to Details View!! I have already checked most of the solutions over here but nothing worked for me :( Please help!
To use a NavigationLink you have to wrap it in a NavigationView
struct ContentView: View {
var body: some View {
NavigationView { // <--- add this
NavigationLink(destination: DetailsView()) {
Text("ADD TO CART")
.font(.headline)
.bold()
.padding(.all, 10)
.padding(.leading, 10)
.padding(.trailing, 10)
.foregroundColor(.white)
.background(Color.orange.frame(width: 300, height: 50) .clipShape(RoundedRectangle(cornerRadius: 20)))
}
}
}
}

Resources