How to recognise tap of Toggle Button for SwiftUI? - ios

I have passed a constant value to Toggle since I want to perform certain acctions on value change of toggle and it needs to be done on Tap Gesture as internally I might need to change the value of toggle as well. But hus code is not working
#State private var toggle = false
var body: some View {
VStack {
Toggle(isOn: .constant(toggle)) {
Text("Hello World")
}
.padding()
.onTapGesture {
print("Tapped")
self.toggle.toggle()
}
}
}
}

Toggle(isOn: Binding(
get:{isToggled},
set:{v in
isToggled = v
customAction()
})) {
Text("Status")
}
Custom action code does not get triggered if the state is modified. But will be triggered on UI interaction

Related

How to despawn a Button and spawn a scrollView xcode swiftui [duplicate]

How do I toggle the presence of a button to be hidden or not?
We have the non-conditional .hidden() property; but I need the conditional version.
Note: we do have the .disabled(bool) property available, but not the .hidden(bool).
struct ContentView: View {
var body: some View {
ZStack {
Color("SkyBlue")
VStack {
Button("Detect") {
self.imageDetectionVM.detect(self.selectedImage)
}
.padding()
.background(Color.orange)
.foreggroundColor(Color.white)
.cornerRadius(10)
.hidden() // ...I want this to be toggled.
}
}
}
}
I hope hidden modifier gets argument later, but since then, Set the alpha instead:
#State var shouldHide = false
var body: some View {
Button("Button") { self.shouldHide = true }
.opacity(shouldHide ? 0 : 1)
}
For me it worked perfectly to set the frame's height to zero when you do not want to see it. When you want to have the calculated size, just set it to nil:
SomeView
.frame(height: isVisible ? nil : 0)
If you want to disable it in addition to hiding it, you could set .disabled with the toggled boolean.
SomeView
.frame(height: isVisible ? nil : 0)
.disabled(!isVisible)
You can utilize SwiftUI's new two-way bindings and add an if-statement as:
struct ContentView: View {
#State var shouldHide = false
var body: some View {
ZStack {
Color("SkyBlue")
VStack {
if !self.$shouldHide.wrappedValue {
Button("Detect") {
self.imageDetectionVM.detect(self.selectedImage)
}
.padding()
.background(Color.orange)
.foregroundColor(Color.white)
.cornerRadius(10)
}
}
}
}
}
The benefit of doing this over setting the opacity to 0 is that it will remove the weird spacing/padding from your UI caused from the button still being in the view, just not visible (if the button is between other view components, that is).
all the answers here works specifically for a button to be hidden conditionally.
What i think might help is making a modifier itself conditionally e.g:
.hidden for button/view, or maybe .italic for text, etc..
Using extensions.
For text to be conditionally italic it is easy since .italic modifier returns Text:
extension Text {
func italicConditionally(isItalic: Bool) -> Text {
isItalic ? self.italic() : self
}
}
then applying conditional italic like this:
#State private var toggle = false
Text("My Text")
.italicConditionally(isItalic: toggle)
However for Button it is tricky, since the .hidden modifier returns "some view":
extension View {
func hiddenConditionally(isHidden: Bool) -> some View {
isHidden ? AnyView(self.hidden()) : AnyView(self)
}
}
then applying conditional hidden like this:
#State private var toggle = false
Button("myButton", action: myAction)
.hiddenConditionally(isHidden: toggle)
You can easily hide a view in SwiftUI using a conditional statement.
struct TestView: View{
#State private var isVisible = false
var body: some View{
if !isVisible {
HStack{
Button(action: {
isVisible.toggle()
// after click you'r view will be hidden
}){
Text("any view")
}
}
}
}
}
It isn't always going to be a pretty solution, but in some cases, adding it conditionally may also work:
if shouldShowMyButton {
Button(action: {
self.imageDetectionVM.detect(self.selectedImage)
}) {
Text("Button")
}
}
There will be an issue of the empty space in the case when it isn't being shown, which may be more or less of an issue depending on the specific layout. That might be addressed by adding an else statement that alternatively adds an equivalently sized blank space.
#State private var isHidden = true
VStack / HStack
if isHidden {
Button {
if !loadVideo(),
let urlStr = drill?.videoURL as? String,
let url = URL(string: urlStr) {
player = VideoPlayerView(player: AVPlayer(), videoUrl: url)
playVideo.toggle()
}
} label: {
Image(playVideo ? "ic_close_blue" : "ic_video_attached")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50)
}
.buttonStyle(BorderlessButtonStyle())
}
.onAppear {
if shouldShowButton {
isHidden = false
} else {
isVideoButtonHidden = true
}
}

Youtube like/dislike buttons function in SwiftUI

Firstly, I am a complete beginner in Swift and SwiftUI.
I am trying to create buttons or toggle feature similar to that of the youtube like/dislike function.
I can create the buttons but I'm struggling with, how to turn button A off when button B is toggled on, and vice versa. whilst also maintaining there individual on/off functionality when clicked.
I have seen similar questions being answered but not for SwiftUI
Thanks in advance!
struct ContentView: View{
#State var isOnGreen = false
#State var isOnRed = false
var body: some View {
HStack{
VStack {
Toggle(isOn: $isOnRed, label: {
Image (systemName: "arrowtriangle.down")
})
.toggleStyle(.button)
.tint(.red)
VStack {
Toggle(isOn: $isOnGreen, label: {
Image (systemName: "arrowtriangle.up")
})
.toggleStyle(.button)
.tint(.green)
}
Add a modifier to your HStack that monitors for the change in isOnRed or isOnGreen:
var body: some View {
HStack {
// ...
}
.onChange(of: isOnGreen) { val in
if val { isOnRed = false }
}
.onChange(of: isOnRed) { val in
if val { isOnGreen = false }
}
}

How to make toggle switch always ON in swift?

I want to make the state of toggle switch to ON always, even if user tries to make it OFF, it should not change. I tried to use .isUserInterstionEnabled = .false, but that didn't work. Can somebody help me on this? Thank you in advance
Try using toggle.isEnabled = false
1.Make the switch outlet in the view controller.
2.Create IBAction of switch and set-:
self.swithCtrl.setOn(true, animated: false)
User will try to disable it but it will remain enable.
There are two ways that I see you can achieve what you want.
Disable it, using the modifier .disabled() (the user will see it slightly faded):
struct Example: View {
#State private var isOn = true
var body: some View {
VStack {
Toggle("Text of toggle", isOn: $isOn)
.disabled(true)
}
}
}
Force it to go back to on, using the modifier .onChange(of:):
struct Example: View {
#State private var isOn = true
var body: some View {
VStack {
Toggle("Text of toggle", isOn: $isOn)
}
.onChange(of: isOn) { _ in
isOn = true
}
}
}

How can I tap the Toggle under a View in SwiftUI?

There are two views (Toggle and Button) under a view (Rectangle):
struct ContentView: View {
#State var val = false
var body: some View {
ZStack {
VStack {
Text("Control penetration")
.font(.title)
Toggle(isOn: $val){EmptyView()}
Button(action: {
print("Yes! Clicked!")
}){
Text("Can you click me???")
}
}
.padding()
Rectangle()
.fill(Color.green.opacity(0.2))
.allowsHitTesting(false)
}
}
}
I use allowsHitTesting() to make the click penetrate to the bottom.
But only the Button can respond to click, the Toggle can not!
What's wrong with it? How can I make the Toggle respond click too? thanks a lot!
Here is possible workaround - give it explicit tap gesture handler. Tested with Xcode 12 / iOS 14
Toggle(isOn: $val){EmptyView()}
.onTapGesture {
withAnimation { val.toggle() }
}

Child subviews not resetting when a new parent is created in SwiftUI

I made a post about this yesterday and apologize for it not being clear or descriptive enough. Today I've made some more progress on the problem but still haven't found a solution.
In my program I have a main view called GameView(), a view called KeyboardView() and a view called ButtonView().
A ButtonView() is a simple button that displays a letter and when pressed tells the keyboardView it belongs to what letter it represents. When it's pressed it is also toggled off so that it cannot be pressed again. Here is the code.
struct ButtonView: View {
let impactFeedbackgenerator = UIImpactFeedbackGenerator(style: .medium)
var letter:String
var function:() -> Void
#State var pressed = false
var body: some View {
ZStack{
Button(action: {
if !self.pressed {
self.pressed = true
self.impactFeedbackgenerator.prepare()
self.impactFeedbackgenerator.impactOccurred()
self.function()
}
}, label: {
if pressed{
Text(letter)
.font(Font.custom("ComicNeue-Bold", size: 30))
.foregroundColor(.white)
.opacity(0.23)
} else if !pressed{
Text(letter)
.font(Font.custom("ComicNeue-Bold", size: 30))
.foregroundColor(.white)
}
})
}.padding(5)
}
}
A keyboard view is a collection of ButtonViews(), one for each button on the keyboard. It tells the GameView what button has been pressed if a button is pressed.
struct KeyboardView: View {
#Environment(\.parentFunction) var parentFunction
var topRow = ["Q","W","E","R","T","Y","U","I","O","P"]
var midRow = ["A","S","D","F","G","H","J","K","L"]
var botRow = ["Z","X","C","V","B","N","M"]
var body: some View {
VStack{
HStack(){
ForEach(0..<topRow.count, id: \.self){i in
ButtonView(letter: self.topRow[i], function: {self.makeGuess(self.topRow[i])})
}
}
HStack(){
ForEach(0..<midRow.count, id: \.self){i in
ButtonView(letter: self.midRow[i], function: {self.makeGuess(self.midRow[i])})
}
}
HStack(){
ForEach(0..<botRow.count, id: \.self){i in
ButtonView(letter: self.botRow[i], function: {self.makeGuess(self.botRow[i])})
}
}
}
}
func makeGuess(_ letter:String){
print("Keyboard: Guessed \(letter)")
self.parentFunction?(letter)
}
}
Finally a GameView() is where the keyboard belongs. It displays the keyboard along with the rest of the supposed game.
struct GameView: View {
#Environment(\.presentationMode) var presentation
#State var guessedLetters = [String]()
#State var myKeyboard:KeyboardView = KeyboardView()
var body: some View {
ZStack(){
Image("Background")
.resizable()
.edgesIgnoringSafeArea(.all)
.opacity(0.05)
VStack{
Button("New Game") {
self.newGame()
}.font(Font.custom("ComicNeue-Bold", size: 20))
.foregroundColor(.white)
.padding()
self.myKeyboard
.padding(.bottom, 20)
}
}.navigationBarTitle("")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
.environment(\.parentFunction, parentFunction)
}
func makeGuess(_ letter:String){
self.guessedLetters.append(letter)
}
func newGame(){
print("Started a new game.")
self.guessedLetters.removeAll()
self.myKeyboard = KeyboardView()
}
func parentFunction(_ letter:String) {
makeGuess(letter)
}
}
struct ParentFunctionKey: EnvironmentKey {
static let defaultValue: ((_ letter:String) -> Void)? = nil
}
extension EnvironmentValues {
var parentFunction: ((_ letter:String) -> Void)? {
get { self[ParentFunctionKey.self] }
set { self[ParentFunctionKey.self] = newValue }
}
}
The issue is that when I start a new game, the array is reset but not keyboardView(), the buttons that have been toggled off remain off, but since it's being replaced by a new keyboardView() shouldn't they go back to being toggled on?
I'll repeat what I said in an answer to your previous question - under most normal use cases you shouldn't instantiate views as variables, so if you find yourself doing that, you might be on the wrong track.
Whenever there's any state change, SwiftUI recomputes the body and reconstructs the view tree, and matches the child view states to the new tree.
When it detects that something has changed, it realizes that the new child view is truly new, so it resets its state, fires .onAppear and so forth. But when there's no change that it can detect, then it just keeps the same state for all the descendent views.
That's what you're observing.
Specifically, in your situation nothing structurally has changed - i.e. it's still:
GameView
--> KeyboardView
--> ButtonView
ButtonView
ButtonView
...
so, it keeps the state of ButtonViews as is.
You can signal to SwiftUI that the view has actually changed and that it should be updated by using an .id modifier (documentation isn't great, but you can find more info in blogs), where you need to supply any Hashable variable to it that's different than the current one, in order to reset it.
I'll use a new Bool variable as an example:
struct GameView {
#State var id: Bool = false // initial value doesn't matter
var body: some View {
VStack() {
KeyboardView()
.id(id) // use the id here
Button("new game") {
self.id.toggle() // changes the id
}
}
}
}
Every time the id changes, SwiftUI resets the state, so all the child views', like ButtonViews', states are reset.

Resources