SwiftUI: Animate resizing of .sheet (macOS) - 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)
}
}

Related

Set the width of a resizable window with a Button in MacOS SwiftUI

How can I set the width of a window that shall be resizable with a Button in MacOS SwiftUI?
I tried with the frame modifier, but that sets the width permanently.
I want the user to be able to drag the Sitze with the mouse and set a predefined size via a Button.
struct ResizeTest: App {
var body: some Scene {
WindowGroup {
ContentView()
.frame(minWidth: 200, maxWidth: 1000)
.padding(0)
}
.defaultSize(width: 300, height: 400)
.windowResizability(.contentSize)
}
}
struct ContentView: View {
#State var width = 400.0
var body: some View {
VStack (alignment: .center) {
Text("width: \(width)")
Button("600"){ width = 600}
}
.frame(width:width )
.background(Color.green)
}
}
Ok that was a little tricky ... its a bit of a hack, but it does work: The buttons set both minWidth and maxWidth to the desired value to force a resize of the window, then reset the values back to the full possible resize range (200-1000) after 0.5 secs.
#main
struct ResizeTest: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.windowResizability(.contentSize)
}
}
struct ContentView: View {
#State var minWidth = 200.0
#State var maxWidth = 1000.0
var body: some View {
GeometryReader { geo in
VStack (alignment: .center) {
Text("Current width: \(geo.size.width.formatted())")
.frame(maxWidth: .infinity)
Button("400"){
minWidth = 400 // set
maxWidth = 400
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // reset after 0.5 secs
minWidth = 200
maxWidth = 1000
}
}
Button("600"){
minWidth = 600 // set
maxWidth = 600
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // reset after 0.5 secs
minWidth = 200
maxWidth = 1000
}
}
}
}
.frame(height: 100)
.frame(minWidth: minWidth, maxWidth: maxWidth)
.background(Color.green)
}
}

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
}
}
}
}
}

How to add animation to sheet dismissal

Presenting a modal sheet in SwiftUI MacOS has a nice slide in animation, but when dismissed it just disappear.
I would like to add a slide out animation to the dismissal of the sheet.
Adding the slideout on the content of the sheet obviously doesn’t work, as the content slides out, but the sheet frame remains until dismissed.
I might be missing something obvious here, but how can I add animation to the sheet itself?
struct ModalSheet: View {
#Binding var visible: Bool
#State var isShowing: Bool = true
var body: some View {
let s = "Click me to dismiss " + String(isShowing);
VStack {
Text(s)
}
.frame(width: 200, height: 100, alignment: .center)
.background(Color.green.opacity(0.5))
.onTapGesture {
isShowing.toggle()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
visible.toggle()
}
}
.offset(y: self.isShowing ? 0 : CGFloat(-110)) // <-- this should be on the sheet itself
.animation(self.isShowing ? .none : .default)
}
}
struct ModalTestAnimation: View {
#State var visible: Bool = false;
var body: some View {
VStack {
Text("Click me")
.onTapGesture {
visible = true
}
}
.frame(width: 250, height: 150)
.sheet(isPresented: $visible, content: {
ModalSheet(visible: $visible)
})
}
}
I did take a look at this, but it doesn’t work for me
SwiftUI sheet not animating dismissal on macOS Big Sur

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?

How to auto-resize a SwiftUI sheet on macOS to fit its content?

Consider this code that presents a sheet on macOS:
struct ContentView: View {
#State var showSheet = false
var body: some View {
VStack {
Text("Hello, world!").padding()
Button("Show sheet") {
showSheet = true
}
}
.sheet(isPresented: $showSheet) {
SheetView()
}
}
}
struct SheetView: View {
#State var greenBiggerBox = false
var body: some View {
VStack {
Toggle("Show bigger box", isOn: $greenBiggerBox)
if greenBiggerBox {
Rectangle().frame(width: 640, height: 480).foregroundColor(.green)
} else {
Rectangle().frame(width: 320, height: 240).foregroundColor(.red)
}
}.padding()
}
}
The problem: initially, the sheet is presented, sized correctly to its contents (good). However, when something happens inside the sheet that resizes the content (like the toggle in the sheet), the sheet window is not resized, yielding a sheet size that is either too large or too small.
How do I make the sheet resize correctly and automatically if its the size of its content changes?

Resources