Transition with a view with SwiftUI without animating the entire view - swift

Using SwiftUI, I have a view that includes a slider that I'm using a transition to slide in from the bottom. All works well, until the slider is moved quickly back and forth. With that, the text field is being animated, and will show "..." when changing from 1 to two digits.
Here is my test code showing this:
struct TestSliderView: View {
#State private var val: Double = 0
#State private var showSlider: Bool = false
var body: some View {
VStack {
Button(action: {
self.showSlider.toggle()
}) {
Text("Show Slider")
}
Spacer()
if showSlider {
JustTheSlider(val: $val)
.padding()
.transition(.move(edge: .bottom))
.animation(.linear(duration: 0.4))
}
}
}
}
struct JustTheSlider: View {
#Binding var val: Double
var body: some View {
VStack {
Text("Slider")
.font(.title)
HStack {
Text("Value: ")
.frame(minWidth: 80, alignment: .leading)
Slider(value: $val, in: 0...30, step: 1)
Text("\(Int(val))")
.frame(minWidth: 20, alignment: .trailing)
.font(Font.body.monospacedDigit())
.padding(.horizontal)
}
}
}
}
One way around this would be to remove the .animation(.linear(duration: 0.4)) line and wrap the button action with an animation like so:
Button(action: {
withAnimation(.linear(duration: 0.4)) {
self.showSlider.toggle()
}
}) {
Text("Show Slider")
}
This stops the text from animating, but then the view only slides out, and just pops in without any slide animation.
Any ideas?

You need animate the state variable, not the View.
var body: some View {
VStack {
Button(action: {
withAnimation{
self.showSlider.toggle()}
}) {
Text("Show Slider")
}
Spacer()
if showSlider {
JustTheSlider(val: $val)
.padding()
.transition(.move(edge: .bottom))
}
}
}
As the last line shown.

Related

Disable to Change Page On Swipe of Tab View in SwiftUI

I am using Tab View in my SwiftUI app. I want the changing of page disabled, while swiping left or right. And I had achieved it from this. This works fine but the issue I am facing is I have a button on the bottom of every view, and when I try to swipe from the button, it is swiping left right. I want to disable it too but don't know how to do it. Here is my code:
struct TabViewTesting: View {
#State var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
firstView().tag(0).contentShape(Rectangle()).gesture(DragGesture())
secondView().tag(1).contentShape(Rectangle()).gesture(DragGesture())
thirdView().tag(2).contentShape(Rectangle()).gesture(DragGesture())
}.tabViewStyle(.page(indexDisplayMode: .never))
}
}
And this is the code for the Views:
extension TabViewTesting {
func firstView() -> some View {
VStack {
Text("First screen")
Spacer()
Button {
self.tabSelection = 1
} label: {
ZStack {
RoundedRectangle(cornerRadius: 20)
.frame(height: 50)
Text("move to 2nd view")
.foregroundColor(.white)
}
}.padding()
}.background(.green)
}
func secondView() -> some View {
VStack {
Text("second screen")
Spacer()
Button {
self.tabSelection = 2
} label: {
ZStack {
RoundedRectangle(cornerRadius: 20)
.frame(height: 50)
Text("move to 3rd view")
.foregroundColor(.white)
}
}.padding()
}.background(.red)
}
func thirdView() -> some View {
VStack {
Text("Third screen")
Spacer()
Button {
self.tabSelection = 0
} label: {
ZStack {
RoundedRectangle(cornerRadius: 20)
.frame(height: 50)
Text("move to first view")
.foregroundColor(.white)
}
}.padding()
}.background(.yellow)
}
}
And this is what happening:
I actually found an answer in the comments of this question.
The issue is: any view that has an "onTapGesture" will ignore ".gesture(DragGesture())".
The solution is to use ".simulataneousGesture(DragGesture())" instead to ensure the gesture is capture and handled by both view/modifier.
It worked perfectly in my case after changing it. The only exception is for 2 finger drag gesture.

Blur view when button is pressed swiftui

I have a view with a toolbar on the bottom of the view. When clicked - two buttons are displayed. I am trying to achieve when the toolbar is pressed and the buttons are now displayed, the view (or background) becomes blurred/grayed out, except for the newly produced items.
I attached a screenshot of the desired effect I am aiming for.
struct UserDashController: View {
// #State private var showMealView = false
#State private var showSettingsView = false
#State private var showAddViews = false
#State private var angle: Double = 0
init(){
UIToolbar.appearance().barTintColor = UIColor.white
}
var body: some View {
NavigationView {
VStack{
Text("Blue me Please")
.frame(width: 400, height:600)
.background(.orange)
}
//sets setting bar top right
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
VStack{
Button(action: {
showSettingsView.toggle()
}) {
Image(systemName: "line.3.horizontal")
.font(.title3)
.foregroundColor(.black)
}
.sheet(isPresented: $showSettingsView){
JournalEntryMain()
}
}
}
// sets add meal option bottom/center
ToolbarItem(placement: .bottomBar) {
//displaying add meal and recipe icons when clicked
HStack{
Button(action: {
angle += 90
showAddViews.toggle()
}) {
if showAddViews {
VStack{
AddToolbar(showAddOptions: $showAddViews)
.offset(y:-50)
}
}
Image(systemName: "plus.square")
.opacity(showAddViews ? 0.5 : 1)
.font(.largeTitle)
.foregroundColor(.black)
.rotationEffect(.degrees(angle))
.animation(.easeIn(duration: 0.25), value: angle)
}
}
}
}
}
}
}
Buttons that appear when toolbar is pressed
struct AddToolbar: View {
#Binding var showAddOptions: Bool
#State var showMealView = false
var body: some View {
HStack{
VStack{
Button(action: {
showMealView.toggle()
}){
VStack{
Image(systemName: "square.and.pencil")
.font(.title)
.foregroundColor(.black)
.background(Circle()
.fill(.gray)
.frame(width:50, height:50))
.padding(3)
Text("Meal")
.foregroundColor(.black)
}
}.fullScreenCover(isPresented: $showMealView){
JournalEntryMain()
}
}
VStack{
Image(systemName: "text.book.closed")
.foregroundColor(.black)
.font(.title)
.background(Circle()
.fill(.gray)
.frame(width:50, height:50))
.padding(3)
Text("Recipe")
.foregroundColor(.black)
}
.offset(y: -50)
}
.frame(height:150)
}
}
Desired Effect
I'm a little confused by your desired effect example, partially because in the UI screenshot you attached, the background isn't blurred, it's just darkened. So, the following answer isn't tailored to your specific example but still should be able to help.
Let's say whatever variable you're using to determine whether or not to show the toolbar is showSettingsView. You could put the following modifiers on your background view:
To blur: .blur(showSettingsView ? 0.5 : 0.0)
To darken: .brightness(showSettingsView ? -0.5 : 0.0)
Obviously just replace "0.5" with whatever number feels best.

Choppy Animation SwiftUI Nested Views

I'm working on an animation that brings up a view from the bottom of the screen above part of the view that previously occupied the screen. My code is technically working, though I'm concerned that the animation looks too choppy. Basically, what I think is happening is that the new, rising view is composed of several other views, and when I animate it coming up, it also animates the sub-views coming together--something I don't like the look of.
Sample Code:
struct ButtonView: View {
#State var show: Bool = false
var body: some View {
ZStack{
VStack {
Button(action: { withAnimation(.linear(duration: 0.5)) { show = !show }} ) {
Text("Press Me")
}
Rectangle()
.foregroundColor(.gray)
}
}
if show {
VStack {
CollapsibleView()
}
}
}
}
struct CollapsibleView: View {
var body: some View {
ZStack {
VStack {
Text("Text 1")
Text("Text 2")
Text("Text 3")
}
.background(Color.white)
}
}
}
Note that the duration is set to be quite long for illustration purposes, but even at smaller duration values I can still notice the choppy effect.
How do I avoid this? Is there a way to just animate the motion?
Here a way for what you may looking for:
struct ContentView: View {
#State var show: Bool = Bool()
var body: some View {
VStack {
Button(action: { show.toggle() }, label: { show ? Text("hide") : Text("show") })
.animation(nil)
Color.gray
Group {
if show { CollapsibleView().transition(.asymmetric(insertion: .move(edge: .bottom), removal: .move(edge: .bottom))) }
}
.opacity(show ? 1.0 : 0.0)
}
.animation(Animation.spring(response: 0.4, dampingFraction: 0.4, blendDuration: 1.0), value: show)
}
}
struct CollapsibleView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Text 1")
Text("Text 2")
Text("Text 3")
}
.background(Color.white)
}
}

How to animate navigationBarHidden in SwiftUI?

struct ContentView: View {
#State var hideNavigationBar: Bool = false
var body: some View {
NavigationView {
ScrollView {
VStack {
Rectangle().fill(Color.red).frame(height: 50)
.onTapGesture(count: 1, perform: {
withAnimation {
self.hideNavigationBar.toggle()
}
})
VStack {
ForEach(1..<50) { index in
HStack {
Text("Sample Text")
Spacer()
}
}
}
}
}
.navigationBarTitle("Browse")
.navigationBarHidden(hideNavigationBar)
}
}
}
When you tap the red rectangle it snaps the navigation bar away. I thought withAnimation{} would fix this, but it doesn't. In UIKit you would do something like this navigationController?.setNavigationBarHidden(true, animated: true).
Tested in xCode 12 beta 6 and xCode 11.7
You could try using
.navigationBarHidden(hideNavigationBar).animation(.linear(duration: 0.5)) instead of .navigationBarHidden(hideNavigationBar)
and also move self.hideNavigationBar.toggle() out of the animation block. That is not required if you use the above approach for hiding of navigation bar with animation.
I think, the only solution is to use a position function in SwiftUI 2
var body: some View {
GeometryReader { geometry in
NavigationView {
ZStack {
Color("background")
.ignoresSafeArea()
// ContentView
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(leading: logo, trailing: barButtonItems)
.toolbar {
ToolbarItem(placement: .principal) {
SearchBarButton(placeholder: LocalizedStringKey("home_vc.search_bar.placeholder"))
.opacity(isNavigationBarHidden ? 0 : 1)
.animation(.easeInOut(duration: data.duration))
}
}
}
.frame(height: geometry.size.height + (isNavigationBarHidden ? 70 : 0))
// This is the key ⬇
.position(x: geometry.size.width/2, y: geometry.size.height/2 - (isNavigationBarHidden ? 35 : 0))
.animation(.easeInOut(duration: 0.38))
.onTapGesture {
isNavigationBarHidden.toggle()
}
}
}
I'm still learning animation in SwiftUI but at this stage, I understand that you must animate the parent view.
So your code would become...
struct ContentView: View {
#State var hideNavigationBar: Bool = false
var body: some View {
NavigationView {
ScrollView {
VStack {
Rectangle().fill(Color.red).frame(height: 50)
.onTapGesture(count: 1) {
self.hideNavigationBar.toggle()
}
VStack {
ForEach(1..<50) { index in
HStack {
Text("Sample Text")
Spacer()
}
}
}
}
}
.navigationBarTitle("Browse")
.navigationBarHidden(hideNavigationBar)
.animation(.spring()) // for example
}
}
}
Note that the last argument in any function call can be placed into a single closure.
So...
.onTapGesture(count: 1, perform: {
self.hideNavigationBar.toggle()
})
can become...
.onTapGesture(count: 1) {
self.hideNavigationBar.toggle()
}
Simpler syntax in my humble opinion.

SwiftUI: Can't get the transition of a DetailView to a ZStack in the MainView to work

I can't find the answer to this anywhere, hopefully one of you can help me out.
I have a MainView with some content. And with the press of a button I want to open a DetailView. I am using a ZStack to layer the DetailView on the top, filling the screen.
But with the following code I can't get it to work. The DetailView does not have a transition when it inserts and it stops at removal. I have tried with and without setting the zIndex manually, and a custom assymetricalTransition. Couldn't get that to work. Any solutions?
//MainView
#State var showDetail: Bool = false
var body: some View {
ZStack {
VStack {
Text("Hello MainWorld")
Button(action: {
withAnimation(.spring()) {
self.showDetail.toggle()
}
}) {
Text("Show detail")
}
}
if showDetail {
ContentDetail(showDetail: $showDetail)
.transition(.move(edge: .bottom))
}
}
.edgesIgnoringSafeArea(.all)
}
And here is the DetailView:
//DetailView
#Binding var showDetail: Bool
var body: some View {
VStack (spacing: 25) {
Text("Hello, DetailWorld!")
Button(action: { withAnimation(.spring()) {
self.showDetail.toggle()
}
}) {
Text("Close")
}
.padding(.bottom, 50)
}
.frame(width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height)
.background(Color.yellow)
.edgesIgnoringSafeArea(.all)
}
The result of this code is this:
I'm running Xcode 11.4.1 so implicit animations doesn't seem to work either. Really stuck here, hope one of you can help me out! Thanks :)
Here is a solution. Tested with Xcode 11.4 / iOS 13.4.
struct MainView: View {
#State var showDetail: Bool = false
var body: some View {
ZStack {
Color.clear // extend ZStack to all area
VStack {
Text("Hello MainWorld")
Button(action: {
self.showDetail.toggle()
}) {
Text("Show detail")
}
}
if showDetail {
DetailView(showDetail: $showDetail)
.transition(AnyTransition.move(edge: .bottom))
}
}
.animation(Animation.spring()) // one animation to transitions
.edgesIgnoringSafeArea(.all)
}
}
struct DetailView: View {
#Binding var showDetail: Bool
var body: some View {
VStack (spacing: 25) {
Text("Hello, DetailWorld!")
Button(action: {
self.showDetail.toggle()
}) {
Text("Close")
}
.padding(.bottom, 50)
}
.frame(maxWidth: .infinity, maxHeight: .infinity) // fill in container
.background(Color.yellow)
}
}