SwiftUI: withAnimation not working with App #State - swiftui

I have a #State var opacity at the App level (i.e. not in a View). I would like to animate its changes. The changes do get applied (causing re-render), but it doesn't animate. I could only get animation to work when I moved that state var into the view.
Is there a way to get withAnimation to work with App state?
My reason for storing that state at the App level is, I also want to pass it to the Settings scene.
This does not work:
#main
struct MacosPlaygroundApp: App {
#State private var opacity: Double = 1.0
var body: some Scene {
WindowGroup {
ContentView(opacity: opacity, onClick: {
withAnimation(.linear(duration: 1)) {
opacity -= 0.5
}
})
}
}
}
struct ContentView: View {
let opacity: Double
let onClick: () -> Void
var body: some View {
Button("Press here") {
onClick()
}
.padding()
.opacity(opacity)
}
}
This works:
The opacity-change animates for both the button in ContentView and the shape in ChildView.
#main
struct MacosPlaygroundApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State private var opacity: Double = 1.0
var body: some View {
VStack {
Button("Press here") {
withAnimation(.linear(duration: 1)) {
opacity -= 0.5
}
}
ChildView(opacity: opacity)
}
.padding()
.opacity(opacity)
}
}
struct ChildView: View {
let opacity: Double
var body: some View {
Color.pink
.contentShape(Rectangle())
.opacity(opacity)
.frame(width: 200, height: 200)
}
}

Related

How I can add a drawing function in SwiftUI?

I can't draw. Unfortunately I don't know where the problem is, colour selection works and the canva is also created, only the drawing doesn't work. I have also checked that the size of the canva is correct and that the path is defined correctly. here is my code:
//
import SwiftUI
struct ContentView: View {
#State private var lines: [Line] = []
#State private var strokeColor: Color = Color.black
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
VStack {
Canvas(lines: $lines, strokeColor: $strokeColor)
HStack {
Button(action: clearDrawing) {
Text("Clear")
}
ColorPicker("Stroke Color", selection: $strokeColor)
}
.padding()
}
}
}
func clearDrawing() {
lines.removeAll()
}
}
//
Here I have created the Canva and the button to delete the drawing
//
struct Canvas: View {
#Binding var lines: [Line]
#Binding var strokeColor: Color
var body: some View {
GeometryReader { geometry in
Path { path in
for line in self.lines {
path.move(to: line.points[0])
path.addLines(line.points)
}
}
.stroke(self.strokeColor, lineWidth: 3)
.frame(width: geometry.size.width, height: geometry.size.height)
.gesture(
DragGesture(minimumDistance: 0.1)
.onChanged({ value in
var lastLine = self.lines.last!
let currentPoint = value.location
lastLine.points.append(currentPoint)
})
.onEnded({ value in
self.lines.append(Line())
})
)
}
}
}
struct Line: Identifiable {
var id: UUID = UUID()
var points: [CGPoint] = []
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
//
And here I have made it so that you should actually draw by touch, and that there is a colour selection

SwiftUI gesture state is reset between gestures

I have the following code for a simple square to which I attach a MagnificationGesture to update its state with a pinch to zoom gesture.
import SwiftUI
struct ContentView2: View {
var scale: CGFloat = 1
var magnificationGesture: some Gesture {
MagnificationGesture()
.onChanged { value in
scale = value
}
}
var body: some View {
VStack {
Text("\(scale)")
Spacer()
Rectangle()
.fill(Color.red)
.scaleEffect(self.scale)
.gesture(
magnificationGesture
)
Spacer()
}
}
}
struct ContentView2_Previews: PreviewProvider {
static var previews: some View {
ContentView2()
}
}
However this simple view behaves weird. When I perform the gesture, the scale #State property is succesfully modified. However when I then do another gesture with my hands, the scale property is reset to its initial state, instead of starting from its current value.
Here is a video of what happens. For example, when the red view is very small, performing the gesture, I would expect that it stays small, instead of completely resetting.
How can I get the desired behaviour - that is - the scale property is not reset
I was able to get it working by adding a bit to the code. Check it out and let me know if this works for your use case:
import SwiftUI
struct ContentView2: View {
var magGesture = MagnificationGesture()
#State var magScale: CGFloat = 1
#State var progressingScale: CGFloat = 1
var magnificationGesture: some Gesture {
magGesture
.onChanged { progressingScale = $0 }
.onEnded {
magScale *= $0
progressingScale = 1
}
}
var body: some View {
VStack {
Text("\(magScale)")
Spacer()
Rectangle()
.fill(Color.red)
.scaleEffect(self.magScale * progressingScale)
.gesture(
magnificationGesture
)
Spacer()
}
}
}
struct ContentView2_Previews: PreviewProvider {
static var previews: some View {
ContentView2()
}
}
I solved it by adding another scale and only updating one of them at the end to keep track of the scale
Code
import SwiftUI
struct ContentView2: View {
#State var previousScale: CGFloat = 1
#State var newScale: CGFloat = 1
var magnificationGesture: some Gesture {
MagnificationGesture()
.onChanged { value in
newScale = previousScale * value
}
.onEnded { value in
previousScale *= value
}
}
var body: some View {
VStack {
Text("\(newScale)")
Spacer()
Rectangle()
.fill(Color.red)
.scaleEffect(newScale)
.gesture(
magnificationGesture
)
Spacer()
}
}
}
struct ContentView2_Previews: PreviewProvider {
static var previews: some View {
ContentView2()
}
}

SwiftUI View doesn't change when using button and #State / #Binding

Long story short this is an onboarding view after a user goes through auth. My main navigation of the app uses navigationview but I can't use that for onboarding. I've put a fullscreencover over the main screen for this onboarding stuff.
However, when trying to simply navigate between the views from different files between the onboarding screens, the button doesn't show the other view. I've tried everything under the son from #Appstorage and #Environment stuff but can't get it to work.
Also please note I cut off the bottom of the rest of the file as that had nothing to do with this logic.
import SwiftUI
struct OnboardingTestView: View {
#State var shouldShowOnboarding = true
var body: some View {
if shouldShowOnboarding {
OffsetChoicesView(shouldShowOnboarding: $shouldShowOnboarding)
}
if shouldShowOnboarding == false {
PersonalInfoView()
}
}
}
struct OnboardingTestView_Previews: PreviewProvider {
static var previews: some View {
OnboardingTestView()
}
}
//*********************************************************************
//OffsetChoicesView
struct OffsetChoicesView: View {
#Binding var shouldShowOnboarding: Bool
var body: some View {
ZStack {
Color(#colorLiteral(red: 0.9803921569, green: 0.9568627451, blue: 0.9568627451, alpha: 1)).edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
VStack {
Progress1View()
.padding(.bottom, 40)
.padding(.top)
Spacer()
Button(action: {
shouldShowOnboarding = false
}) {
NextButtonView()
.padding(.top)
.padding(.bottom)
}
}
You can try something like this - utilizes #AppStorage
I was not quite sure what the OffsetChoiceView() parameters are supposed to be.. so I just removed them in the example. This should be whatever your onboarding view is called.
import SwiftUI
struct OnboardingTestView: View {
//#State var shouldShowOnboarding = true
#AppStorage("showOnboarding") var showOnboarding: Bool = true
var body: some View {
if (showOnboarding == true) {
OffsetChoicesView()
} else if (showOnboarding == false) {
PersonalInfoView()
}
}
}
struct OnboardingTestView_Previews: PreviewProvider {
static var previews: some View {
OnboardingTestView()
}
}
//*********************************************************************
//OffsetChoicesView
struct OffsetChoicesView: View {
//#Binding var shouldShowOnboarding: Bool
#AppStorage("showOnboarding") var showOnboarding: Bool = false
var body: some View {
ZStack {
Color(#colorLiteral(red: 0.9803921569, green: 0.9568627451, blue: 0.9568627451, alpha: 1)).edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
VStack {
Progress1View()
.padding(.bottom, 40)
.padding(.top)
Spacer()
Button(action: {
showOnboarding = false
}) {
NextButtonView()
.padding(.top)
.padding(.bottom)
}
}

ignoresSafeArea not covering bottom of View

I'm trying to create the background for a background view for a popup, however the view doesn't cover the bottom even after I use .ignoreSafeArea().
import SwiftUI
struct Overlay<Content: View>: View {
#State var opacity: Double = 0
var content: Content
var body: some View {
VStack {
Color.black.opacity(opacity).ignoresSafeArea()
content
}
.onAppear { self.opacity = 0.5 }
.onDisappear { self.opacity = 0 }
}
}
struct ForgotPasswordView_Previews: PreviewProvider {
static var previews: some View {
Group {
Overlay(content: Text(""))
.previewDevice("iPhone 12")
}
}
}
VStack arranges its children in a vertical line.
What You see in the bottom area is your content which you pass in the view creation (here its your TextView).
struct Overlay<Content: View>: View {
#State var opacity: Double = 0
var content: Content
var body: some View {
ZStack { //<- here
Color.black.opacity(opacity).ignoresSafeArea()
content
}
.onAppear { self.opacity = 0.5 }
.onDisappear { self.opacity = 0 }
}
}
this is the solution i was able to come up with:
import SwiftUI
struct Overlay<Content: View>: View {
#State var opacity: Double = 1
var content: Content
var body: some View {
VStack {
Color.black
.opacity(0)
content
}
.background(Color.black.opacity(opacity))
.ignoresSafeArea(.all)
.onAppear { self.opacity = 0.5 }
.onDisappear { self.opacity = 0 }
}
}
struct ForgotPasswordView_Previews: PreviewProvider {
static var previews: some View {
Group {
Overlay(content: Text("hi"))
.previewDevice("iPhone 12")
}
}
}
Here is an image of it working
if you want the content to be overlayed on top of the view you can use a ZStack with the color in the back and the content in the front. this can be implemented by removeing the V in VStack and replacing it with a Z. Beutiful image of it working!

Why won't my UI reflect a change in the #State property?

I'm trying to make a graph app, but animating it using a #State property does not help, for some reason.
struct GraphBars: View {
#State var percent: CGFloat
var body: some View {
Capsule()
.fill(Color.black)
.frame(width: 50, height: self.percent)
}
}
struct TEST3: View {
#State var bar1: CGFloat = 90.0
var body: some View {
GeometryReader { gg in
VStack {
Button(action: {
self.bar1 = 300.0
}) {
Text("Hello")
}
GraphBars(percent: bar1)
}
However, pressing the button does not increase the height of the bar as I thought it would. What am I doing wrong?
You need to use #Binding to transfer a variable back and forth between two Views otherwise the parent View doesn't get a notice of the variable change.
Do it like this:
struct GraphBars: View {
#Binding var percent: CGFloat
var body: some View {
Capsule()
.fill(Color.black)
.frame(width: 50, height: self.percent)
}
}
struct Test3: View {
#State var bar1: CGFloat = 90.0
var body: some View {
GeometryReader { gg in
VStack {
Button(action: {
self.bar1 = 300.0
}) {
Text("Hello")
}
GraphBars(percent: self.$bar1)
}
}
}
}