How to add animation to sheet dismissal - macos

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

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?

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?

Add Image to TextField/SecureField in SwiftUI, add padding to placeholder text

I made a textfield and a securetextfield in SwiftUI but I have no idea how to add in an image into my textfield/secure textfield in SwiftUI. There is not much documentation online for SwiftUI like there was for the older versions of Swift. I also want to shift over the (placeholder/typed in text) over by a designated amount say for example like 30 points to the right. I also was trying out to see if the background color would change from white to red, but as you can see, it is in my code with no effect on the UI.
Note:I have the GeometryReader called earlier in my code as well as the #state variables for the username and the password.
My goal is to have it look like this , right now it looks like this
VStack (spacing: deviceSize.size.height * (50/812)) {
TextField ("Username", text: self.$username)
.foregroundColor(.black)//text color when you type
.accentColor(.blue)//cursor color
.background(Color(.red))//????
.textFieldStyle(RoundedBorderTextFieldStyle())
.cornerRadius(50)
// .border(Color.white)
//.font(.title)
SecureField ("Password", text: self.$password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.cornerRadius(50)
}
.padding(.init(top: 0, leading: deviceSize.size.width * (38/375), bottom: 0, trailing: deviceSize.size.width * (38/375)))
The easiest way to achieve such a design would be to place the Image and TextField in a HStack and give it one Rounded background. It is slightly more complicated with the password field as it needs an extra Button, and when you hide/show the password you need to change between TextField and SecureField. Here is my take on it:
struct ContentView: View {
#State private var username = ""
#State private var password = ""
#State private var showPassword = false
var body: some View {
ZStack {
Color.blue
VStack {
HStack {
Image(systemName: "person")
.foregroundColor(.secondary)
TextField("Username",
text: $username)
} .padding()
.background(Capsule().fill(Color.white))
HStack {
Image(systemName: "lock")
.foregroundColor(.secondary)
if showPassword {
TextField("Password",
text: $password)
} else {
SecureField("Password",
text: $password)
}
Button(action: { self.showPassword.toggle()}) {
Image(systemName: "eye")
.foregroundColor(.secondary)
}
} .padding()
.background(Capsule().fill(Color.white))
} .padding()
}
}
}
I'm really new to SwiftUI, but I found a workaround for this that I hope doesn't cause any issues in the future or it will be a big lesson learned. If anyone has any suggestion I'd appreciate it too! =]
I embedded the TextField and the image in a ZStack and I put the image inside a View and gave the view a padding.
struct FormInputBox: View {
#State private var text: String = ""
#State private var textFieldState: TextFieldState = .empty
private var textFieldType: TextFieldType
private var textViewPlaceholder = ""
init(placeholder: String,
textFieldType: TextFieldType) {
self.textViewPlaceholder = placeholder
self.textFieldType = textFieldType
}
var body: some View {
ZStack(alignment: Alignment(horizontal: .trailing, vertical: .center), content: {
TextField(textViewPlaceholder, text: $text)
.textFieldStyle(MyTextFieldStyle(textFieldState: $textFieldState))
AnyView(
Image("tick")
.resizable()
.frame(width: 20, height: 20, alignment: .leading)
)
.padding(32)
})
}
I have created a reusable SwiftUI Textfield named ASTextField which works similar to the textField in UIKit, where you can add the leftView and rightView of the textField and can handle the events related them.
You can find the implementation of this at gist.
This the way you can consume it:-
struct ContentView : View , ASTextFieldDelegate {
let leftImage = UIImage(systemName: "calendar")
let rightImage = UIImage(systemName: "eye")
let rightImage1 = UIImage(systemName: "trash")
#State var text : String? = "with simple binding"
#State var text1 : String? = "with closure for right item"
#State var text2 : String? = "for secure entry"
var body: some View {
VStack {
Spacer()
ASTextField(text: $text)
Spacer()
ASTextField(rightItem: rightImage1, leftItem: leftImage, handleLeftTap: {
print("right icon tapped.....")
}, delegate: self, text: $text1)
Spacer()
ASTextField(rightItem: rightImage, leftItem: leftImage, isSecuredEntry: true, delegate: self, text: $text2)
Spacer()
}
}
}
"Introspect" will work for you
Textfield()
.introspectTextField { textfield in
textfield.rightViewMode = .unlessEditing
textfield.rightView = UIImageView(image: UIImage(named: ImageCatalog.error.content))
}
I am totally newborn toddle in iOS Dev. So i wrote just like this. My apologises in advance if someone will get blind from the ugliness of the written code.
struct ContentView: View {
#State private var nameSearch: String = ""
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 25)
.frame(width: 230, height: 30)
.border(.black, width: 0.2)
.foregroundColor(.white)
HStack {
ZStack {
Image(systemName: "magnifyingglass.circle")
.foregroundColor(.gray)
.frame(width: 10, height: 10, alignment: .leading)
.padding(.trailing, 200)
TextField( "Search", text: $nameSearch)
.frame(width: 180, height: 30)
.padding(.leading, 20 )
}
}
}

Resources