SwiftUI: save the state of toggle and keep the animation - swift

In SwiftUI, for this code to toggle the display of view:
#State var show = true
Button { withAnimation { show.toggle() }}
label: { Image(systemName: show ? "chevron.down" : "chevron.right") }
if show { ... }
The animation will be shown if the show is the #State variable.
However, I found that if show is changed to #AppStorage (so to keep the show state), the animation will not be shown.
Is there a way to keep the show state and also preserve the animation?

You can also replace the withAnimation {} with the .animation(<#T##animation: Animation?##Animation?#>, value: <#T##Equatable#>) modifier and then it seems to work directly with the #AppStorage wrapped variable.
import SwiftUI
struct ContentView: View {
#AppStorage("show") var show: Bool = true
var body: some View {
VStack {
Button {
self.show.toggle()
}
label: {
Rectangle()
.fill(Color.red)
.frame(width: self.show ? 200 : 400, height: 200)
.animation(.easeIn, value: self.show)
}
Rectangle()
.fill(Color.red)
.frame(width: self.show ? 200 : 400, height: 200)
.animation(.easeIn, value: self.show)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
EDIT: Following the comments, another solution
import SwiftUI
struct ContentView: View {
#State private var show: Bool
init() {
self.show = UserDefaults.standard.bool(forKey: "show")
// Or self._show = State(initialValue: UserDefaults.standard.bool(forKey: "show"))
}
var body: some View {
VStack {
Button {
withAnimation {
self.show.toggle()
}
}
label: {
Text("Toggle")
}
if show {
Rectangle()
.fill(Color.red)
.frame(width: 200 , height: 200)
}
}
.padding()
.onChange(of: self.show) { newValue in
UserDefaults.standard.set(newValue, forKey: "show")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

Having trouble with showing another view

I'm currently trying to input another listview in my contentView file to test if it'll show, but for some reason it isn't showing the list. I'm having a bit of trouble understanding why this is happening as I am not receiving any error message.
This is the code for the list file
import SwiftUI
extension Image{
func anotherImgModifier() -> some View{
self
.resizable()
.scaledToFill()
.frame( width: 75, height: 75)
.cornerRadius(9)
}
}
struct PokeListView: View {
#State var imgURL: String = ""
#EnvironmentObject var pokeWebService: PokeWebService
//functions
// func loadImage() async -> [Image]{
// for
// }
var body: some View {
NavigationView {
List( pokeWebService.pokeList?.results ?? [], id: \.id){ pokemon in
NavigationLink(destination: PokeDetailsView(urlString: pokemon.url, counter: 4, name: pokemon.name)) {
AsyncImage(url:URL(string: "https://play.pokemonshowdown.com/sprites/bw/\(pokemon.name).png")){ image in
image.anotherImgModifier()
}
placeholder: {
Image(systemName: "photo.circle.fill").iconModifer()
}.padding(40)
Text(pokemon.name.uppercased()).font(.system(size: 15, weight: .heavy, design: .rounded))
.foregroundColor(.gray)
.task{
do{
try await pokeWebService.getPokemonFromPokemonList(from: pokemon.url)
} catch{
print("---> task error: \(error)")
}
}
}
}
}
.task {
do{
try await pokeWebService.getPokemonList()
} catch{
print("---> task error: \(error)")
}
}
}
}
struct PokeListView_Previews: PreviewProvider {
static var previews: some View {
PokeListView()
.previewLayout(.sizeThatFits)
.padding()
.environmentObject(PokeWebService())
}
}
This is the code for the ContentView where I was trying to input the list file.
import SwiftUI
struct ContentView: View {
#StateObject var newsWebService = NewsWebService()
#StateObject var pokeWebService = PokeWebService()
let gbImg = Image("pokeball").resizable()
#State private var gridLayout: [GridItem] = [ GridItem(.flexible()), GridItem(.flexible())]
#State private var gridColumn: Int = 2
#State var selection: Int? = nil
var body: some View {
NavigationView{
ScrollView(.vertical, showsIndicators: false, content: {
VStack(alignment: .center, spacing: 15, content: {
Spacer()
NewsCapsule()
//GRID
//BERRIES, POKEMON, GAMES
GroupBox(label: Label{
Text("PokéStuff")
} icon: {
Image("pokeball").resizable().scaledToFit().frame(width: 30, height: 30, alignment: .leading)
}
, content: {
PokeListView()
}).padding(.horizontal, 20).foregroundColor(.red)
})//:VSTACK
})//:SCROLLVIEW
.navigationBarTitle("Pokemon",displayMode: .large)
.toolbar(content: {
ToolbarItem(placement: .navigationBarTrailing, content: {
Image(systemName: "moon.circle")
.resizable()
.scaledToFit()
.font(.title2)
.foregroundColor(.red)
})
})
}//:NAVIGATIONBAR
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(NewsWebService())
.environmentObject(PokeWebService())
}
}
How would I get to fix this?
EDIT-1:
with further tests, this is what worked for me:
in ContentView, add .frame(height: 666) to the VStack {...}.
This is the reason why you do not see anything. You need a frame height.
Also in ContentView, add .environmentObject(pokeWebService) to the NavigationView,
and just use PokeListView(). This is to pass the pokeWebService
to that view. After that, all works for me. You may want to experiment
with different frame sizes and such likes. You should also remove the NavigationView from your PokeListView, there is no need for it.

Implementing Button in side menu

can someone Help me with fixing this. I want this code to work such as when I click the Home button on the side menu, it should take me to the Main View("This is the Main View"). I have tried using presenting sheets, however, presenting sheet doesn't look realistic. When the Home button is tapped, everything should disappear and only the Home Screen should come up with the side menu. I have tried writing up this code, however, I couldn't make the home button work. The codes are as below:
import SwiftUI
import Foundation
import Combine
struct Home: View {
#State var showMenu = false
#EnvironmentObject var userSettings: UserSettings
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
withAnimation {
self.showMenu = false
}
}
}
return NavigationView {
GeometryReader {
geometry in
ZStack(alignment: .leading) {
MainView(showMenu: self.$showMenu)
.frame(width: geometry.size.width, height: geometry.size.height)
.offset(x: self.showMenu ? geometry.size.width/2 : 0)
.disabled(self.showMenu ? true : false)
if self.showMenu {
MenuView()
.frame(width: geometry.size.width/2)
.transition(.move(edge: .leading))
}
}
.gesture(drag)
}
.navigationBarTitle("Pay Data", displayMode: .inline)
.navigationBarItems(leading: (Button(action: {
withAnimation {
self.showMenu.toggle()
}
}){
Image(systemName: "line.horizontal.3")
.imageScale(.large)
}
))
}
}
}
struct MainView: View {
#Binding var showMenu: Bool
#EnvironmentObject var userSettings: UserSettings
var body: some View {
Text("This is Main View")
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
.environmentObject(UserSettings())
}
}
//This is the Menu View. The Home Button is located in this view.
import SwiftUI
import Combine
import Foundation
struct MenuView: View {
#EnvironmentObject var userSettings: UserSettings
#State var showMenu = false
#State var Homevariable = false
var body: some View {
VStack(alignment: .leading) {
Button(action: {
UserDefaults.standard.set(false, forKey: "status")
}) {
(Text(Image(systemName: "rectangle.righthalf.inset.fill.arrow.right")) + (Text("Home")))
}
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(red: 32/255, green: 32/255, blue: 32/255))
.edgesIgnoringSafeArea(.all)
}
}
struct MenuView_Previews: PreviewProvider {
static var previews: some View {
MenuView()
.environmentObject(UserSettings())
}
}
//This is the another view. I want the side Menu to appear on this as well, so when I press the Home button it takes me to the Main View("This is the Main View")
import SwiftUI
struct Calculation: View {
var body: some View {
Text("Hello, World!")
}
}
struct Calculation_Previews: PreviewProvider {
static var previews: some View {
Calculation()
}
}
Here you go. You are basically rebuilding a navigation logic, so in MainView you have to switch between the screens and put the side menu over it:
(PS: you can do without GeometryReader)
struct ContentView: View {
#State private var showMenu = false
#State private var selected: SelectedScreen = .home
var body: some View {
NavigationView {
ZStack {
// show selected screen
switch selected {
case .home:
MainView()
.disabled(self.showMenu ? true : false)
case .screen1:
OtherView(screen: 1)
case .screen2:
OtherView(screen: 2)
}
// put menu over it
if self.showMenu {
MenuView(showMenu: $showMenu, selected: $selected)
.transition(.move(edge: .leading))
}
}
.navigationBarTitle("Pay Data", displayMode: .inline)
// .navigationBarItems is deprecated, use .toolbar
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
self.showMenu.toggle()
}
} label: {
Image(systemName: "line.horizontal.3")
.imageScale(.large)
}
}
}
}
}
}
enum SelectedScreen {
case home
case screen1
case screen2
}
struct MenuView: View {
#Binding var showMenu: Bool
#Binding var selected: SelectedScreen
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 24) {
Button {
selected = .home
showMenu = false
} label: {
Label("Home", systemImage: "rectangle.righthalf.inset.fill.arrow.right")
}
Button {
selected = .screen1
showMenu = false
} label: {
Label("Screen 1", systemImage: "1.circle")
}
Button {
selected = .screen2
showMenu = false
} label: {
Label("Screen 2", systemImage: "2.circle")
}
}
.padding()
.frame(maxHeight: .infinity)
.background(Color(red: 32/255, green: 32/255, blue: 32/255))
Spacer()
}
}
}
struct MainView: View {
var body: some View {
Text("This is Main View")
.font(.largeTitle)
}
}
struct OtherView: View {
let screen: Int
var body: some View {
Text("Other View: Screen \(screen)")
.font(.largeTitle)
}
}

SwiftUI - Sheet Dismmis button not working

I want to add a button on the top of my view that will be the "X" to close the sheet. I am unable to use presentationmode option. For some reason it isn't working. Can someone please let me know how I can add a button that shows an "X" on the top that if I click on will close this view. Your help is very much appreciated.
import SwiftUI
struct TopicsExperienceCards: View {
// #Environment(\.presentationMode) var presentationMode
let etype: EItype
var body: some View {
NavigationView{
/* Alernatively Page Layout View */
ScrollView (.vertical, showsIndicators: false) {
Rectangle()
.fill(Color(etype.accentcolor))
.frame(width: 300, height: 5)
.padding()
GroupBox {
TabView {
ForEach(etype.content1,id: \.self) {item in
VStack (alignment:.center, spacing:0){
Text(item)
.padding()
.frame(width:300, height:300, alignment:.center)
Divider()
Spacer()
Text("Room for an image")
Spacer()
Spacer()
}
} //foreach
} //: TABVIEW
.tabViewStyle(PageTabViewStyle())
.onAppear {
setupAppearance() }
} //end of GroupBox
// .padding()
.frame(width:350, height:650)
.clipShape(RoundedRectangle(cornerRadius: 25.0, style: .circular))
.shadow(radius: 5)
} //end of ScrollView
.edgesIgnoringSafeArea(.all)
} //end of Navigation view
}
}
/* Function for the black dots in pagination */
func setupAppearance() {
UIPageControl.appearance().currentPageIndicatorTintColor = .black
UIPageControl.appearance().pageIndicatorTintColor = UIColor.black.withAlphaComponent(0.2)
}
struct TopicsExperienceCards_Previews: PreviewProvider {
static let etypes: [EItype] = Bundle.main.decode("eibasestructure.json")
static var previews: some View {
TopicsExperienceCards(etype:etypes[1])
}
}
You can just pass in the Binding that you use to present the sheet to TopicsExperienceCards as well.
struct ContentView: View {
#State var isPresented = false
var body: some View {
Button("Present") {
isPresented = true /// set Binding to true to present
}
.sheet(isPresented: $isPresented) {
TopicsExperienceCards(isPresented: $isPresented) /// pass Binding here
}
}
}
struct TopicsExperienceCards: View {
#Binding var isPresented: Bool
var body: some View {
VStack {
HStack {
Spacer()
Button(action: {
isPresented = false /// set Binding back to false
}) {
Image(systemName: "xmark")
.padding()
}
}
Spacer()
}
}
}
Result:

Missing argument for parameter in call, Binding value issue

I am trying to create a binding value in a view. Whenever I do this is gives a compiling error in the PreviewProvider. The error is shown in the last code bit.
I cant add the argument (showProfile: $showProfile) because it cannot find it in the scope.
struct ContentView: View {
#State var showMenu = false
#Binding var showProfile: Bool
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
withAnimation {
self.showMenu = false
}
}
}
//MARK: View Start
return NavigationView {
GeometryReader { geo in
ZStack(alignment: .leading) {
MainView()
.frame(width: geo.size.width, height: geo.size.height)
.offset(x: self.showMenu ? geo.size.width/2 : 0)
.disabled(self.showMenu ? true : false)
if self.showMenu {
MenuView()
.frame(width: geo.size.width/2)
.transition(.move(edge: .leading))
}
}
.gesture(drag)
} .navigationBarItems(leading: (
Button(action: {
withAnimation {
self.showMenu.toggle()
}
}) {
Image(systemName: "line.horizontal.3")
.imageScale(.large)
}
)
)}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView() // <---- Error, missing argument for parameter 'showProfile' in call
}
}
Because you have defined a #Binding on ContentView, you need to always provide a value for it. In a preview, you'll most likely want to pass it a .constant value:
ContentView(showProfile: .constant(true))

How do I implement #EnvironmentObject for this custom full-screen modal setup?

My goal is to have custom modals present over an root view that is essentially a tabbed view. So, I wrapped the TabView in a ZStack and am using an ObservableOBject. But I don't feel I'm doing it the right way.
In my other file, I have the Custom modal "subviews" which has an enum, too, which I think is the right approach to take. But I cannot figure out how to dismiss a modal after it is visible.
It must be #EnvironmentObject, but I don't know what if anything to put in the scene delegate, etc. ("Hacking with Swift" is failing me here, although it's a great resource.)
My idea is that views from the tabbed view will have various buttons which present different modal views, populated later with data specific to say a user and set of fields for data entry.
Right now, I just want to understand how to present and dismiss them.
Here is my root view
import SwiftUI
struct ContentView: View {
#ObservedObject var modal = CustomModal()
var body: some View {
ZStack {
TabView {
ZStack {
Color.pink.opacity(0.2)
Button(action: {
withAnimation{
self.modal.visibleModal = VisibleModal.circle
}
}) {
Text("Circle").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.pink.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "1.square.fill")
Text("One")
}
}.tag(1)
ZStack {
Color.blue.opacity(0.2)
Button(action: {
self.modal.visibleModal = VisibleModal.squircle
}) {
Text("Square").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.blue.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "2.square.fill")
Text("Two")
}
}.tag(2)
}.accentColor(.purple)
VStack {
containedView()
}
}
}
func containedView() -> AnyView {
switch modal.visibleModal {
case .circle: return AnyView(CircleView())
case .squircle: return AnyView(SquircleView())
case .none: return AnyView(Text(""))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And here is my second file with the enum and "subview" modals
import SwiftUI
class CustomModal: ObservableObject {
#Published var visibleModal: VisibleModal = VisibleModal.none
}
enum VisibleModal {
case circle, squircle, none
}
struct CircleView: View {
var body: some View {
ZStack {
Color.pink.blur(radius: 0.4)
Circle().fill()
.frame(width: 300)
.foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct SquircleView: View {
var body: some View {
ZStack{
Color.green.blur(radius: 0.4)
RoundedRectangle(cornerRadius: 48, style: .continuous)
.frame(width: 300, height: 300).foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct dismissButton: View {
#ObservedObject var modal = CustomModal()
var body: some View {
VStack{
Spacer()
Button(action: {
self.modal.visibleModal = VisibleModal.none
}) {
Text("Dismiss").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.white.opacity(0.35)).foregroundColor(.white)
.cornerRadius(12)
.padding(.bottom, 44)
}
}
}
Are you just trying to pass your observable object to the new view?
func containedView() -> some View {
switch modal.visibleModal {
case .circle: return CircleView()
.environmentObject(self.modal)
case .squircle: return SquircleView()
.environmentObject(self.modal)
case .none: return Text("")
}
}
Unless I am misunderstanding the question.
Okay, after a lot of fiddling, it works.
Now my code is as follows.
Root view
struct ContentView: View {
#EnvironmentObject var isModalVisible: CustomModal
#ObservedObject var modal = CustomModal()
var body: some View {
ZStack {
TabView {
ZStack {
Color.pink.opacity(0.2)
Button(action: {
withAnimation{
self.isModalVisible.isModalVisible.toggle()
self.modal.currentModal = VisibleModal.circle
}
}) {
Text("Circle").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.pink.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "1.square.fill")
Text("One")
}
}.tag(1)
ZStack {
Color.blue.opacity(0.2)
Button(action: {
self.isModalVisible.isModalVisible.toggle()
self.modal.currentModal = VisibleModal.squircle
}) {
Text("Square").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.blue.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "2.square.fill")
Text("Two")
}
}.tag(2)
}.accentColor(.purple)
if self.isModalVisible.isModalVisible {
VStack {
containedView()
}
}
}
}
func containedView() -> AnyView {
switch modal.currentModal {
case .circle: return AnyView(CircleView())
case .squircle: return AnyView(SquircleView())
case .none: return AnyView(Text(""))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(CustomModal())
}
}
and the second file with the supporting views and classes and enums:
import SwiftUI
class CustomModal: ObservableObject {
#Published var isModalVisible = false
#Published var currentModal: VisibleModal = .none
}
enum VisibleModal {
case circle, squircle, none
}
struct CircleView: View {
#EnvironmentObject var env: CustomModal
var body: some View {
ZStack {
Color.pink.blur(radius: 0.4)
Circle().fill()
.frame(width: 300)
.foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct SquircleView: View {
var body: some View {
ZStack{
Color.green.blur(radius: 0.4)
RoundedRectangle(cornerRadius: 48, style: .continuous)
.frame(width: 300, height: 300).foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct dismissButton: View {
#EnvironmentObject var env: CustomModal
var body: some View {
VStack{
Spacer()
Button(action: {
self.env.isModalVisible.toggle()
print("TAPPED")
}) {
Text("Dismiss").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.white.opacity(0.35)).foregroundColor(.white)
.cornerRadius(12)
.padding(.bottom, 44)
}
}
}
It still can be refactored. I'm sure. I'd also be happy to hear any comments on how to improve it. But it seems to work.
NOTE: This code ContentView().environmentObject(CustomModal()) is put in the previewP{rovider code and in SceneDelegate.