SwiftUI - All variables toggle instead of juts one - toggle

I have 5 state variables and 5 button.
I want them in a row, and to toggle them one by one
Variables
#State private var canEat: Bool = false
#State private var canDrink: Bool = false
#State private var canSleep: Bool = false
#State private var canDance: Bool = false
#State private var canEntertain: Bool = false
Button
Section(header: Text("Action".uppercased())) {
HStack {
Button(action: {self.canEat.toggle()}){Image("ToEat").foregroundColor(self.canEat ? .green : .red)}
Spacer()
Button(action: {self.canDrink.toggle()}){Image("ToDrink").foregroundColor(self.canDrink ? .green : .red)}
Spacer()
Button(action: {self.canSleep.toggle()}){Image("ToSleep").foregroundColor(self.canSleep ? .green : .red)}
Spacer()
Button(action: {self.canDance.toggle()}){Image("ToDance").foregroundColor(self.canDance ? .green : .red)}
Spacer()
Button(action: {self.canEntertain.toggle()}){Image("ToEntertain").foregroundColor(self.canEntertain ? .green : .red)}
Spacer()
}
}
The issue is when i toggle one, They all toggle together ...
If i create another VStack below and add an acxtual toggle
VStack{
Toggle(isOn: $canEat) {
Image("ToEat")
}
}
then i'm able to toggle only one.
Idea why ? and how i could resolve this ?
THanks,
Nicolas

You can move action to a TapGesture.
Form{
Section(header: Text("Action".uppercased())) {
HStack {
Button(action: {}){Image("image").foregroundColor(self.canEat ? .green : .red)}.onTapGesture {
self.canEat.toggle()
}
Spacer()
Button(action: {}){Image("image").foregroundColor(self.canDrink ? .green : .red)}.onTapGesture {
self.canDrink.toggle()
}
Spacer()
Button(action: {}){Image("image").foregroundColor(self.canSleep ? .green : .red)}.onTapGesture {
self.canSleep.toggle()
}
Spacer()
Button(action: {}){Image("image").foregroundColor(self.canDance ? .green : .red)}.onTapGesture {
self.canDance.toggle()
}
Spacer()
Button(action: {}){Image("image").foregroundColor(self.canEntertain ? .green : .red)}.onTapGesture {
self.canEntertain.toggle()
}
Spacer()
}
}
}

Related

How to change language of the app with toggles in SwiftUI

I have a view with list of toggles with languages that should change current app language after the restart. I understand how to create this feature with buttons, but it doesn't work the same way with toggles. I've tried to use on change, but it's not allowing to turn off the toggle after the second tap. How to do that properly?
struct Languages: View {
#State private var currentLanguage = true
#State private var currentLanguageEnglish = true
#State private var currentLanguageRussian = false
#State private var showingAlert = false
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#State var currentSysLanguage = UserDefaults.standard.string(forKey: "language")
var body: some View {
VStack(alignment: .leading) {
DoubleTextView(topText: LocalizedStringKey("languages"), buttomText: "", topTextSize: 24, buttomTextSize: 0)
// Works just right, wrong design.
Button("English", action: {
currentSysLanguage = "en"
UserDefaults.standard.set(currentSysLanguage, forKey: "language")
showingAlert.toggle()
})
.alert("Restart your app", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
}
Button("French", action: {
currentSysLanguage = "fr"
UserDefaults.standard.set(currentSysLanguage, forKey: "language")
showingAlert.toggle()
})
.alert("Restart your app", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
}
// Does not work, this is how it should be.
ZStack(alignment: .top) {
Toggle(isOn: $currentLanguageEnglish) {
Text("English")
.font(.custom("Manrope-Bold", size: 16))
.foregroundColor(.white)
}
.padding(.horizontal)
.tint(Color("active"))
}
.padding(.vertical, 30.0)
.background(Rectangle()
.fill(Color("navigation"))
.frame(height: 50)
.cornerRadius(8))
ZStack(alignment: .top) {
Toggle(isOn: $currentLanguageRussian) {
Text("French")
.font(.custom("Manrope-Bold", size: 16))
.foregroundColor(.white)
}
.padding(.horizontal)
.tint(Color("active"))
}
.background(Rectangle()
.fill(Color("navigation"))
.frame(height: 50)
.cornerRadius(8))
Spacer()
}
.background(
Image("background2")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
)
.onChange(of: currentLanguageEnglish, perform: { newValue in
currentLanguageRussian = true
})
.onChange(of: currentLanguageRussian, perform: { newValue in
currentLanguageEnglish = true
})
.padding(.top, 40)
.overlay {
HStack {
Spacer()
Button {
self.mode.wrappedValue.dismiss()
} label: {
HStack {
Image(systemName: "arrow.left")
.foregroundColor(.white)
.font(.system(size: 24))
Spacer()
}
}
}
.padding(.horizontal, 3.0)
.frame(maxHeight: .infinity, alignment: .top)
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
.padding(.horizontal, 10.0)
}
}
There are many ways you can accomplish something similar, one is to make the toggle bind to a custom binding object:
Toggle(isOn: .init(get: {
currentSysLanguage == "en"
}, set: { isOn in
currentSysLanguage = isOn ? "en" : defaultLanguage
})) {
Text("English")
}
Here the getter makes the toggle listen to currentSysLanguage == "en". It will be on/off based on if the statement evaluates true. Whereas the setter will be triggered when you manually toggle it, and set currentSysLanguage between "en" and defaultLanguage.
Note I have added the defaultLanguage to represent the state when all toggles are off.
You can then create as many toggles as needed with the same code by changing the language code it is comparing to.
Near the end, you would add a .onChange modifier to listen to currentSysLanguage to send the changes to UserDefaults:
.onChange(of: currentSysLanguage, perform: { newValue in
UserDefaults.standard.set(newValue, forKey: "language")
})

Group buttons and change icon color on active selection

So I have a few buttons inside a VStack() which I want to bind or combine with one state property, to determine when each button was clicked.
When a button is clicked, I want to use the .accentColor modifier while the action is active and when the action is done, fallback to the original Color.secondary.
Currently, here is what it's doing:
- When app is opened, it loads the default color.
- When one button is clicked, it turns both buttons into a lighter shade of gray.
- When I exit the view, it turns both buttons to accentColor and keeps them like that.
- When I click on a button again, it turns gray.
Video for reference:
https://im5.ezgif.com/tmp/ezgif-5-d53f89a9af.gif
Could anyone spot what I might be doing wrong?
So I have the following code (Code has been stripped of all the unnecessary items, such as var body: some View, etc..):
#State private var tappedActiveMapAnnotationButton : Bool = false
VStack {
Button(action: {
withAnimation(.spring()) {
self.tappedActiveMapAnnotationButton = true
mainViewModel.showFilters.toggle()
}
}, label: {
Image(systemName: "slider.horizontal.3")
.font(.title3)
.padding(10)
.foregroundColor(tappedActiveMapAnnotationButton ? .accentColor : Color.secondary)
}) //: Button
Button(action: {
withAnimation(.spring()) {
self.tappedActiveMapAnnotationButton = true
showMapDisplaySheet.toggle()
}
}, label: {
Image(systemName: "map")
.font(.title3)
.padding(10)
.foregroundColor(tappedActiveMapAnnotationButton ? .accentColor : Color.secondary)
}) //: Button
.sheet(isPresented: $showMapDisplaySheet) {
MapDisplaySheetView()
.presentationDetents([.fraction(0.25)])
}
} //: VStack
.background {
RoundedRectangle(cornerRadius: 8)
.fill(Color(uiColor: .systemBackground))
}
Create the following enum
enum buttonSelection {
case buttonNone, button1, button2
}
and you essentially create a single state variable of the type buttonSelection and perform whatever you want based on the current value of buttonSelection
#State private var currentSelection : buttonSelection = .buttonNone
VStack {
Button(action: {
withAnimation(.spring()) {
self.currentSelection = .button1
mainViewModel.showFilters.toggle()
}
}, label: {
Image(systemName: "slider.horizontal.3")
.font(.title3)
.padding(10)
.foregroundColor(currentSelection == .button1 ? .accentColor : Color.secondary)
}) //: Button
Button(action: {
withAnimation(.spring()) {
self.currentSelection = .button2
showMapDisplaySheet.toggle()
}
}, label: {
Image(systemName: "map")
.font(.title3)
.padding(10)
.foregroundColor(currentSelection == .button2 ? .accentColor : Color.secondary)
}) //: Button
.sheet(isPresented: $showMapDisplaySheet, onDismiss: {
print("Dismissed")
currentSelection = .buttonNone
}) {
MapDisplaySheetView()
.presentationDetents([.fraction(0.25)])
}
} //: VStack
.background {
RoundedRectangle(cornerRadius: 8)
.fill(Color(uiColor: .systemBackground))
}

How to remove the cornerradius of sheets in swiftui?

Is there a way to remove the cornerRadius of a sheet? I tried it like this:
.sheet(isPresented: $showModal) {
Modal().cornerRadius(0, corners: [.topLeft, .topRight])
}
but it didn't work.
I know I can just use fullScreenCover but I still want to know if there is a solution to this.
According to my comment above you can create your own slide-in menu.
In the example below I added a close button as well as gesture control to close the view.
//
//
// SlideInMenu.swift
// SlideInMenu
//
// Created by Sebastian on 21.09.22.
//
import SwiftUI
var bounds = UIScreen.main.bounds
struct ContentView: View {
#State var selectedItem: String = ""
#State var showMenu = false
var body: some View {
ZStack() {
MainView(selectedItem: $selectedItem, showMenu: $showMenu)
.blur(radius: showMenu ? 3 : 0)
SlideView(selectedItem: $selectedItem, showMenu: $showMenu)
}
.edgesIgnoringSafeArea(.all)
}
}
struct MainView: View {
#Binding var selectedItem: String
#Binding var showMenu: Bool
var body: some View {
HStack(){
Spacer()
VStack() {
Spacer()
Text("This is your main View")
.foregroundColor(.white)
.padding()
Button(action: {
withAnimation(.linear(duration: 0.3)) {
self.showMenu.toggle()
}
}) {
Text("Show Menu")
.font(.system(size: 20, weight: .medium))
.foregroundColor(.white)
}
Spacer()
}
Spacer()
}.background(Color.blue)
}
}
struct SlideView: View {
#Binding var selectedItem: String
#Binding var showMenu: Bool
#State private var viewOffest: CGFloat = 100
#State private var offset = CGSize.zero
#State private var isDragging = false
var body: some View {
let dragGesture = DragGesture()
.onChanged { value in
withAnimation(.linear(duration: 0.2)) {
if value.translation.height >= 0 {
offset = value.translation
}
}
}
.onEnded { _ in
withAnimation(.linear(duration: 0.2)) {
isDragging = false
if offset.height > (bounds.height - viewOffest)/3 {
showMenu.toggle()
}
offset = .zero
}
}
ZStack() {
Color.black
.opacity(showMenu ? 0.5 : 0)
VStack() {
HStack() {
Spacer()
Spacer()
}
VStack(alignment: .leading) {
HStack() {
Spacer()
Text("Here is the menu")
.foregroundColor(.black)
Spacer()
}
HStack() {
Spacer()
Button(action: {
withAnimation(.linear(duration: 0.3)) {
self.showMenu.toggle()
}
}) {
Text("Close Menu")
.font(.system(size: 20, weight: .medium))
.foregroundColor(.red)
}
.padding()
Spacer()
}
Spacer()
}
.padding()
.background(Color.white)
.cornerRadius(0)
}
.offset(y: showMenu ? viewOffest + offset.height : bounds.height)
.gesture(dragGesture)
}
}
}
It is not possible for now perhaps we can in further update, nonetheless you can create your own custom view.

I don't know what to put in the view placeholder

I keep getting an error to insert "proxy: /GeometryProxy/" even when "proxy" is a state variable. I have the code for the preview below. I don't know what to put in the placeholder and I am confused. I have included my full code to review. Please look below.
struct MathematicallyMainController_Previews: PreviewProvider {
static var previews: some View {
MathematicallyMainController(proxy: // what do i put here?)
}
}
My full code:
This is where the problem with the 'var' is happening.
struct MathematicallyMainController: View {
#StateObject var tabBarModel = TabBarViewModel()
#Environment(\.colorScheme) var colorScheme
#State var selectedIndex = 0
#State var isSelectedA = false
#State var isSelectedB = false
#State var isSelectedC = false
#State var isSelectedD = false
#State var isSelectedE = false
#State var proxy: GeometryProxy
var body: some View {
ZStack {
let bottomEdge = proxy.safeAreaInsets.bottom
switch selectedIndex {
case 0:
HomeViewController()
case 1:
BrowseView()
case 2:
RewardsView()
case 3:
EssentialsView()
case 4:
SchoolModeView()
default:
HomeViewController()
}
ZStack {
RoundedRectangle(cornerRadius: 15)
.fill(.regularMaterial)
.colorScheme(colorScheme == .dark ? .dark : .light)
HStack(alignment: .center) {
Button {
selectedIndex = 0
} label: {
if selectedIndex == 0 {
ZStack {
Circle()
.blur(radius: 20)
.foregroundColor(.blue)
.frame(width: 60)
.padding([.leading, .trailing], 5)
Image(systemName: "house.fill")
.font(.title)
.foregroundColor(colorScheme == .dark ? .black : .white)
.padding([.leading, .trailing], 5)
}
} else {
Image(systemName: "house.fill")
.font(.title)
.foregroundColor(colorScheme == .dark ? .white : .black)
}
}
Button {
selectedIndex = 1
} label: {
if selectedIndex == 1 {
ZStack {
Circle()
.blur(radius: 20)
.foregroundColor(.indigo)
.frame(width: 60)
.padding(.leading, 15)
Image(systemName: "rectangle.stack.fill")
.font(.title)
.foregroundColor(colorScheme == .dark ? .black : .white)
.padding(.leading, 15)
}
} else {
Image(systemName: "rectangle.stack.fill")
.font(.title)
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(.leading, 15)
}
}
Button {
selectedIndex = 2
} label: {
if selectedIndex == 2 {
ZStack {
Circle()
.blur(radius: 20)
.foregroundColor(.orange)
.frame(width: 60)
.padding([.trailing, .leading], 15)
Image(systemName: "circle.dotted")
.font(.title)
.foregroundColor(colorScheme == .dark ? .black : .white)
.padding([.trailing, .leading], 15)
}
} else {
Image(systemName: "circle.dotted")
.font(.title)
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(.trailing, 15)
.padding(.leading, 15)
}
}
Button {
selectedIndex = 3
} label: {
if selectedIndex == 3 {
ZStack {
Circle()
.blur(radius: 20)
.foregroundColor(.green)
.frame(width: 60)
.padding(.trailing, 15)
Image(systemName: "doc.text.image")
.font(.title)
.foregroundColor(colorScheme == .dark ? .black : .white)
.padding(.trailing, 15)
}
} else {
Image(systemName: "doc.text.image")
.font(.title)
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(.trailing, 15)
}
}
Button {
selectedIndex = 4
} label: {
if selectedIndex == 4 {
ZStack {
Circle()
.blur(radius: 20)
.foregroundColor(.purple)
.frame(width: 60)
.padding([.leading, .trailing], 5)
Image(systemName: "graduationcap.fill")
.font(.title)
.foregroundColor(colorScheme == .dark ? .black : .white)
.padding([.leading, .trailing], 5)
}
} else {
Image(systemName: "graduationcap.fill")
.font(.title)
.foregroundColor(colorScheme == .dark ? .white : .black)
}
}
}
.colorScheme(colorScheme == .dark ? .dark : .light)
.padding(.horizontal)
}
.frame(height: 60)
.shadow(color: .primary, radius: -10)
.shadow(color: .black, radius: 10)
.padding([.horizontal])
.padding(.bottom)
.frame(maxHeight: .infinity, alignment: .bottom)
.modifier(OffsetModifier())
.environmentObject(tabBarModel)
.offset(y: tabBarModel.tabState == .floating ? 0 : bottomEdge)
}
}
}
This is my ViewModifier I have included to create the scrolling animation.
struct OffsetModifier: ViewModifier {
#EnvironmentObject var model: TabBarViewModel
func body(content: Content) -> some View {
content
.overlay(
GeometryReader { proxy -> Color in
let minY = proxy.frame(in: .global).minY
DispatchQueue.main.async {
let durationOffset: CGFloat = 35
if minY < model.offset {
if model.offset < 0 && -minY > (model.lastStoredOffset + durationOffset) {
withAnimation(.easeOut.speed(1)) {
model.tabState = .floating
}
model.lastStoredOffset = -model.offset
}
}
if minY > model.offset && -minY < (model.lastStoredOffset + durationOffset) {
withAnimation(.easeOut.speed(1)) {
model.tabState = .expanded
}
model.lastStoredOffset = -model.offset
}
model.offset = minY
}
return Color.clear
}
,alignment: .top
)
}
}
The only time you ever use proxy is on this line:
let bottomEdge = proxy.safeAreaInsets.bottom
Therefore, there's no reason to pass in the entire GeometryProxy -- you can just pass in the bottom inset. Also, it definitely doesn't need to be a #State variable -- #State is used when the View needs to mutate its state over time -- this is just a parameter being passed in.
Change this line:
#State var proxy: GeometryProxy
to:
var bottomInset : CGFloat = 0
Because it has a default value, your preview can turn into this:
static var previews: some View {
MathematicallyMainController()
}
If you really wanted to pass a value (say, from the parent of the View), you would do something like this:
GeometryReader { proxy in
//other view code...
MathematicallyMainController(bottomInset: proxy.safeAreaInsets.bottom)
//other code...
}
There's also the option of moving the GeometryReader inside of MathematicallyMainController, but I'm assuming there's a reason you had it outside to begin with.
All of that being said, it looks like you have some unnecessary and unused code that you could clean up. For example, none of the isSelectedA-E variables ever get used. There's probably more here that can be refactored, but is outside of the scope of the present question.

How to align the an image on top of a button in swiftui?

I wish to add a 'trash' image on the top-right side of each button when 'Delete Button' is pressed, so that when user hits the trash image, the button will be removed from the vstack.
I think I should use zstack to position the trash image but I don't know how for now.
Below shows where the trash image should be located in each button.
Also, when I press the 'Delete Button', it seems that each button's text size and spacing with another button is changed slightly. How do I overcome this problem? The button position, spacing, textsize should be unchanged when 'Delete Button' is hit.
struct someButton: View {
#Environment(\.editMode) var mode
#ObservedObject var someData = SomeData()
#State var newButtonTitle = ""
#State var isEdit = false
var body: some View {
NavigationView{
// List{ // VStack
VStack{
VStack{
ForEach(Array(someData.buttonTitles.keys.enumerated()), id: \.element){ ind, buttonKeyName in
//
Button(action: {
self.someData.buttonTitles[buttonKeyName] = !self.someData.buttonTitles[buttonKeyName]!
print("Button pressed! buttonKeyName is: \(buttonKeyName) Index is \(ind)")
print("bool is \(self.someData.buttonTitles[buttonKeyName]!)")
}) {
HStack{ //HStack, ZStack
if self.isEdit{
Image(systemName: "trash")
.foregroundColor(.red)
.onTapGesture{
print("buttonkey \(buttonKeyName) will be deleted")
self.deleteItem(ind: ind)
}
}
Text(buttonKeyName)
// .fontWeight(.semibold)
// .font(.title)
}
}
.buttonStyle(GradientBackgroundStyle(isTapped: self.someData.buttonTitles[buttonKeyName]!))
.padding(.bottom, 20)
}
}
HStack{
TextField("Enter new button name", text: $newButtonTitle){
self.someData.buttonTitles[self.newButtonTitle] = false
self.newButtonTitle = ""
}
}
}
.navigationBarItems(leading: Button(action: {self.isEdit.toggle()}){Text("Delete Button")},
trailing: EditButton())
// .navigationBarItems(leading: Button(action: {}){Text("ergheh")})
// }
}
}
func deleteItem(ind: Int) {
let key = Array(someData.buttonTitles.keys)[ind]
print(" deleting ind \(ind), key: \(key)")
self.someData.buttonTitles.removeValue(forKey: key)
}
}
struct GradientBackgroundStyle: ButtonStyle {
var isTapped: Bool
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.frame(maxWidth: .infinity, maxHeight: 50)
.padding()
.foregroundColor(isTapped ? Color.blue : Color.black)
.background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(40)
.overlay(RoundedRectangle(cornerRadius: 40)
.stroke(isTapped ? Color.blue : Color.black, lineWidth: 4))
.shadow(radius: 40)
.padding(.horizontal, 20)
.scaleEffect(configuration.isPressed ? 0.9 : 1.0)
//
}
}
class SomeData: ObservableObject{
#Published var buttonTitles: [String: Bool] = ["tag1": false, "tag2": false]
}
Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4 (with some replicated code)
var body: some View {
Button(action: { }) {
Text("Name")
}
.buttonStyle(GradientBackgroundStyle(isTapped: tapped))
.overlay(Group {
if self.isEdit {
ZStack {
Button(action: {print(">> Trash Tapped")}) {
Image(systemName: "trash")
.foregroundColor(.red).font(.title)
}.padding(.trailing, 40)
.alignmentGuide(.top) { $0[.bottom] }
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
}
})
.padding(.bottom, 20)
}