SwiftUI transition modifier not working as I believed - animation

I believed that this code would result in the background darkener fading in while the red square scales in, but what actually happens is that both simply fade in. Why does the .transition(.scale) modifier on the red square not cause it to scale in when it is added to the view hierarchy?
struct DemoView: View {
#State
private var flag = false
var body: some View {
Button(
action: {
withAnimation {
flag = true
}
},
label: {
Color.blue
}
)
.overlay(
ZStack {
if flag {
Button(
action: {
withAnimation {
flag = false
}
},
label: {
Color
.black
.opacity(0.5)
}
)
.transition(.opacity)
.overlay(
Color.red
.frame(width: 200, height: 200)
.transition(.scale)
)
}
}
)
.buttonStyle(.plain)
}
}

Cascading does not work here, ie. transition works on read per-view appear. The red color box in really not appeared here, it is appears as part of built parent view.
Here is a fixed part to give effect as you wanted (as I understood).
Tested with Xcode 13.2 / iOS 15.2
.overlay(
ZStack {
if flag {
Button(
action: {
withAnimation {
flag = false
}
},
label: {
Color
.black
.opacity(0.5)
}
)
.transition(.opacity)
}
if flag {
Color.red
.frame(width: 200, height: 200)
.transition(.scale)
}
}
.animation(.default, value: flag)

Related

SwiftUI: Animate resizing of .sheet (macOS)

Is it possible to animate a size change of a sheet? If I animate a size change of the sheet's contents, the sheet itself will immediately assume the new size without animation. See video (white background of sheet should never be visible):
Code:
struct ContentView: View {
#State var isPresented = false
#State var sheetContentHeight = 100.0
var body: some View {
VStack {
Button("Show Sheet") {
isPresented = true
}
}
.frame(width: 500, height: 300)
.sheet(isPresented: $isPresented) {
sheetContent
}
}
var sheetContent: some View {
ZStack {
Rectangle()
.frame(width: 300, height: sheetContentHeight)
.foregroundColor(Color.gray)
Button("Change Content Height") {
sheetContentHeight = 200
}
}
.animation(.easeOut(duration: 1), value: sheetContentHeight)
}
}

Animating changes to #FocusState SwiftUI

I am using the new #FocusState to control how my views react to the user deciding to start inputting information into text fields. My current need is to wrap an animation around my top view leaving the screen as the keyboard moves up. Usually this kind of thing can be accomplished by simply wrapping withAnimation() around a boolean toggle, but since Swift is toggling my focus state bool under the hood, I can't wrap an animation around it in this way. How else should I do it?
Here is a minimal reproducible example. Basically I want to animate the top (red) view leaving / coming back into view with changes to my focus state isFocused var.
struct ContentView: View {
#State var text: String = ""
#FocusState var isFocused: Bool
var body: some View {
ZStack {
VStack {
if !isFocused {
Text("How to Animate this?")
.frame(width: 300, height: 300)
.background(Color.red)
.animation(.easeInOut(duration: 5), value: isFocused)
}
Text("Middle Section")
.frame(width: 300, height: 300)
.background(Color.green)
Spacer()
TextField("placeholder", text: $text)
.focused($isFocused)
}
if isFocused {
Color.white.opacity(0.1)
.onTapGesture {
isFocused = false
}
}
}
}
}
I don't think the animation modifier that's currently on the top view is doing anything, but I imagine that that's where I'll put some animation code.
Here is something that works. I've done this before to make an animation happen upon an #FocusState property changing its value. Can't really tell you why though, it's just something I figured out with trial and error.
struct ContentView: View {
#State var text: String = ""
#FocusState var isFocused: Bool
#State private var showRedView = false
var body: some View {
ZStack {
VStack {
if !showRedView {
Text("How to Animate this?")
.frame(width: 300, height: 300)
.background(Color.red)
}
Text("Middle Section")
.frame(width: 300, height: 300)
.background(Color.green)
Spacer()
TextField("placeholder", text: $text)
.focused($isFocused)
}
.onChange(of: isFocused) { bool in
withAnimation(.easeInOut(duration: 5)) {
showRedView = bool
}
}
if isFocused {
Color.white.opacity(0.1)
.onTapGesture {
isFocused = false
}
}
}
}
}

SwiftUI on macOS: Make subviews in .sheet use the available width

I want to show a sheet with a title and action buttons upon clicking on a button in my macOS app. While this works, I do have an issue with the width of the containing buttons: they only use their intrinsically needed width instead of the width of their parent.
Out of the box, without providing any width values, the sheet looks like this:
I am currently hardcoding the width and applying it to the buttons – which works, but is not really sexy. The sheet then looks like this:
That's more like it. Here' my code:
struct ContentView: View {
#State private var showSheet = false
var body: some View {
VStack {
Text("My View")
Button {
showSheet = true
} label: {
Text("Show Sheet…")
}
}
.sheet(isPresented: $showSheet) {
SheetView(showSheet: $showSheet)
}
}
}
struct SheetView: View {
#Binding var showSheet: Bool
let width = CGFloat(200.0)
var body: some View {
VStack {
Text("FooBar")
Divider()
Button {
showSheet = false
} label: {
Label("Foo", systemImage: "paperclip")
.frame(width: width)
}
Button {
showSheet = false
} label: {
Label("FooBarBuzz", systemImage: "paperclip")
.frame(width: width)
}
Button {
showSheet = false
} label: {
Label("Cancel", systemImage: "xmark")
.frame(width: width)
}
}
.padding()
}
}
My goal though would be to set the width of all subviews in the sheet to the subview with the biggest width – how can I do that?

SwiftUI: Custom Modal Animation

I made a custom modal using SwiftUI. It works fine, but the animation is wonky.
When played in slow motion, you can see that the ModalContent's background disappears immediately after triggering ModalOverlay's tap action. However, ModalContent's Text views stay visible the entire time.
Can anyone tell me how I can prevent ModalContent's background from prematurely disappearing?
Slow-mo video and code below:
import SwiftUI
struct ContentView: View {
#State private var isShowingModal = false
var body: some View {
GeometryReader { geometry in
ZStack {
Button(
action: { withAnimation { self.isShowingModal = true } },
label: { Text("Show Modal") }
)
ZStack {
if self.isShowingModal {
ModalOverlay(tapAction: { withAnimation { self.isShowingModal = false } })
ModalContent().transition(.move(edge: .bottom))
}
}.edgesIgnoringSafeArea(.all)
}
}
}
}
struct ModalOverlay: View {
var color = Color.black.opacity(0.4)
var tapAction: (() -> Void)? = nil
var body: some View {
color.onTapGesture { self.tapAction?() }
}
}
struct ModalContent: View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
VStack(spacing: 16) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.frame(width: geometry.size.width)
.padding(.top, 16)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.background(Color.white)
}
}
}
}
The solution (thanks to #JWK):
It's probably a bug. It seems that, during the transition animation (when the views are disappearing) the zIndex of the two views involved (the ModalContent and the ModalOverlay) is not respected. The ModalContent (that is supposed to be in front of the ModalOverlay) is actually moved under the ModalOverlay at the beginning of the animation. To fix this we can manually set the zIndex to, for example, 1 on the ModalContent view.
struct ContentView: View {
#State private var isShowingModal = false
var body: some View {
GeometryReader { geometry in
ZStack {
Button(
action: { withAnimation { self.isShowingModal = true } },
label: { Text("Show Modal") }
)
ZStack {
if self.isShowingModal {
ModalOverlay(tapAction: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = false } })
ModalContent()
.transition(.move(edge: .bottom))
.zIndex(1)
}
}.edgesIgnoringSafeArea(.all)
}
}
}
}
The investigation that brings to a solution
Transition animations in SwiftUI have still some issues. I think this is a bug. I'm quite sure because:
1) Have you tried to change the background color of your ModalContent from white to green?
struct ModalContent: View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
VStack(spacing: 16) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.frame(width: geometry.size.width)
.padding(.top, 16)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.background(Color.green)
}
}
}
}
This way it works (see the following GIF):
2) Another way to make the bug occur is to change the background color of your ContentView to, for example, green, leaving the ModalContent to white:
struct ContentView: View {
#State private var isShowingModal = false
var body: some View {
GeometryReader { geometry in
ZStack {
Button(
action: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = true } },
label: { Text("Show Modal") }
)
ZStack {
if self.isShowingModal {
ModalOverlay(tapAction: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = false } })
ModalContent().transition(.move(edge: .bottom))
}
}
}
}
.background(Color.green)
.edgesIgnoringSafeArea(.all)
}
}
struct ModalOverlay: View {
var color = Color.black.opacity(0.4)
var tapAction: (() -> Void)? = nil
var body: some View {
color.onTapGesture { self.tapAction?() }
}
}
struct ModalContent: View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
VStack(spacing: 16) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.frame(width: geometry.size.width)
.padding(.top, 16)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.background(Color.white)
}
}
}
}
Even in this case it works as expected:
3) But if you change your ModalContent background color to green (so you have both the ContentView and the ModalContent green), the problem occurs again (I won't post another GIF but you can easily try it yourself).
4) Yet another example: if you change the appearance of you iPhone to Dark Appearance (the new feature of iOS 13) your ContentView will automatically become black and, since your ModalView is white, the problem won't occur and everything goes fine.

SwiftUI round button deforms when clicked

I have created a round button with following code:
struct RoundButton : View {
var value = "..."
var body: some View {
GeometryReader { geometry in
Button(action: {}) {
VStack {
Text(self.value)
.font(.headline)
.color(Color("NightlyDark"))
Text("MIN")
.font(.footnote)
.color(.white)
}
.frame(width: geometry.size.width, height: geometry.size.height)
}
.clipShape(Circle())
}
}
}
But when clicking the button, the shape gets deformed. Any idea why this happens ?
Same happens when i use .mask(Circle())
Is this a beta thing or normal behavior ? And does anyone maybe know a better way to create rounded buttons ?
what happens here is Button Considers all screen[width + height] as their frame by default.
so you have to set Button also.
I think it's the default behavior in watchOS
Here Your Code :
struct RoundButton: View {
var value = "..."
var body: some View {
GeometryReader { geometry in
Button(action: {}) {
VStack {
Text(self.value)
.font(.headline)
.foregroundColor(Color("NightlyDark"))
Text("MIN")
.font(.footnote)
.foregroundColor(.white)
}
.frame(width: geometry.size.width, height: geometry.size.height)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.clipShape(Circle())
}
}
}
Note: i'm using Xcode 11 Beta 4

Resources