SwiftUI View does not disappear instantly after the transition animation ends - ios

I created an application with move transition, which sends a View up from below with SwiftUI. However, when View disappears, transition animation is over, but the View remains for about 1 second.
This is my code.
struct ContentView: View {
#State var animation: Bool = false
var body: some View {
VStack {
// This is the Button
Button(action: {
withAnimation(.spring(dampingFraction: 1, blendDuration: 0.5)) {
animation.toggle()
}
}) {
Image(systemName: "star.fill")
.resizable()
.frame(width: 100, height: 100)
.foregroundColor(.accentColor)
}
// This condition sends up the View
if animation {
SecondView()
.transition(.move(edge: .bottom))
}
}
.padding()
}
}
struct SecondView: View {
var body: some View {
VStack {
Text("Hello, world!")
.font(.largeTitle)
.fontWeight(.bold)
Spacer()
}
}
}
And this happened.

I think what you need to do is to apply the animation directly to your button by using the animation modifier so your code may look like this:
Button(action: {
withAnimation {
animation.toggle()
}
}) {
Image(systemName: "star.fill")
.resizable()
.frame(width: 100, height: 100)
.foregroundColor(.accentColor)
}
.animation(.spring(dampingFraction: 1, blendDuration: 0.5), value: animation)
if animation {
SecondView()
.transition(.move(edge: .bottom))
}

Related

How to clip a view while using a SwiftUI .move transition / animation

I'm trying to animate in a view and make it appear as if it's a sort of drawer opening from another view. This is all fine except if the first view is not opaque. It appears that you can see the animating view the moment it begins animating. Is there a way to clip this so it appears that the view is growing from the top of the bottom view?
Even without opacity this is an issue if where you're animating in from isn't a covered (demoed in second gif)
Sample Code:
struct ContentView: View {
#State private var showingSecondView: Bool = false
var body: some View {
VStack(spacing: 0) {
Spacer()
if showingSecondView {
ZStack {
Color.green.opacity(0.25)
Text("Second View")
}
.frame(width: 300, height: 300)
.transition(.move(edge: .bottom))
}
ZStack {
Color.black.opacity(1)
Text("First View")
}
.frame(width: 300, height: 300)
Button("Animate In / Out") {
showingSecondView.toggle()
}
.padding()
}
.animation(.easeInOut, value: showingSecondView)
}
}
It is possible to do by clipping exact container of 'drawer'. Here is a demo of possible approach.
Tested with Xcode 13.2 / iOS 15.2 (Simulator slow animation is ON for better demo)
var body: some View {
VStack(spacing: 0) {
Spacer()
VStack {
if showingSecondView {
ZStack {
Color.green.opacity(0.25)
Text("Second View")
}
.transition(.move(edge: .bottom))
} else {
Color.clear // << replacement for transition visibility
}
}
.frame(width: 300, height: 300)
.animation(.easeInOut, value: showingSecondView) // << animate drawer !!
.clipped() // << clip drawer area
ZStack {
Color.black.opacity(0.2)
Text("First View")
}
.frame(width: 300, height: 300)
Button("Animate In / Out") {
showingSecondView.toggle()
}
.padding()
}
}
Here a way for you:
struct ContentView: View {
#State private var isSecondViewPresented: Bool = false
var body: some View {
VStack(spacing: 0) {
Spacer()
ZStack {
Color.green.opacity(0.25).cornerRadius(20)
Text("Second View")
}
.frame(width: 300, height: 300)
.offset(y: isSecondViewPresented ? 0 : 300)
.clipShape(RoundedRectangle(cornerRadius: 20))
ZStack {
Color.black.opacity(0.1).cornerRadius(20)
Text("First View")
}
.frame(width: 300, height: 150)
Button("Animate In / Out") {
isSecondViewPresented.toggle()
}
.padding()
}
.animation(.easeInOut, value: isSecondViewPresented)
}
}

Implementing custom sidebar across all views - SwiftUI

OUTLINE
I have made a custom slimline sidebar that I am now implementing across the whole app. The sidebar consists of a main button that is always showing and when pressed it shows or hides the rest of the sidebar that consists of buttons navigating to other views.
I am currently implementing the sidebar across the app on each view by creating a ZStack like this:
struct MainView: View {
var body: some View {
ZStack(alignment: .topLeading) {
SideBarCustom()
Text("Hello, World!")
}
}
}
PROBLEM
I am planning on adding a GeometryReader so if the side bar is shown the rest of the content moves over. With this in mind, the way I am implementing the sidebar on every view feels clunky and a long winded way to add it. Is there a more simple/better method to add this to each view?
Sidebar Code:
struct SideBarCustom: View {
#State var isToggle = false
var names = ["Home", "Products", "Compare", "AR", "Search"]
var icons = ["house.fill", "printer.fill.and.paper.fill", "list.bullet.rectangle", "arkit", "magnifyingglass"]
var imgSize = 20
var body: some View {
GeometryReader { geo in
VStack {
Button(action: {
self.isToggle.toggle()
}, label: {
Image("hexagons")
.resizable()
.frame(width: 40, height: 40)
.padding(.bottom, 20)
})
if isToggle {
ZStack{
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color.red)
.frame(width: 70, height: geo.size.height)
VStack(alignment: .center, spacing: 60) {
ForEach(Array(zip(names, icons)), id: \.0) { item in
Button(action: {
// NAVIIGATE TO VIEW
}, label: {
VStack {
Image(systemName: item.1)
.resizable()
.frame(width: CGFloat(imgSize), height: CGFloat(imgSize))
Text(item.0)
}
})
}
}
}
}
}
}
}
}
I don't think there's necessarily a reason to use GeometryReader here. The following is an example that has a dynamic width sidebar (although you could set it to a fixed value) that slides in and out. The main content view resizes itself automatically, since it's in an HStack:
struct ContentView : View {
#State private var sidebarShown = false
var body: some View {
HStack {
if sidebarShown {
CustomSidebar(sidebarShown: $sidebarShown)
.frame(maxHeight: .infinity)
.border(Color.red)
.transition(sidebarShown ? .move(edge: .leading) : .move(edge: .trailing) )
}
ZStack(alignment: .topLeading) {
MainContentView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
if !sidebarShown {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
}
}
}
}
}
struct CustomSidebar : View {
#Binding var sidebarShown : Bool
var body: some View {
VStack {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
Spacer()
Text("Hi")
Text("There")
Text("World")
Spacer()
}
}
}
struct MainContentView: View {
var body: some View {
VStack {
Text("Main content")
}
}
}

how can I hide tab bar in specific screens in SwiftUI?

How to hide the tabBar in specific screens? I'm navigating from login to directly to tabBar. Is there any way to hide? In UIKit we're hiding by pushing and I have no idea how to do it in SwiftUI, by presenting the view not going to work.
Here is my TabBar
struct ReceiverTabBar: View {
#State private var selection: Int = 0
var body: some View {
TabView(selection: $selection){
.tabItem {
selection == 0 ? Image("")
Text("")
}
.tag(0)
ReceiverProfileView()
.tabItem {
selection == 1 ? Image("")
Text("")
}
.tag(1)
ReceiverNotificationsView()
.tabItem {
selection == 2 ? Image("")
Text("")
}
.tag(2)
ReceiverMoreView()
.tabItem {
selection == 3 ? Image("")
Text("")
}
.tag(3)
}
.accentColor(.black)
}
}
and I want hide tabBar in this view
struct MakingDonationView: View {
#Environment(\.presentationMode) var presentationMode
#State var selected = 0
var body: some View {
ScrollView(showsIndicators: false) {
Image("")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.horizontal,30)
.padding(.top,40)
.frame(height: UIScreen.main.bounds.height/5)
Text("")
.font(.custom("Poppins-SemiBold", size: 16))
.foregroundColor(Color("#252422"))
.padding(.top,20)
Text("")
.font(.custom("Poppins-SemiBold", size: 12))
.foregroundColor(Color("#5E5E5E"))
Text("")
.font(.custom("Poppins-Medium", size: 12))
.foregroundColor(Color("#A0A0A0"))
}
Spacer()
Divider()
MakingDonation(selected: $selected)
}
.padding(.all)
}
.padding(.horizontal,20)
.edgesIgnoringSafeArea(.bottom)
}
Button(action: {
}, label: {
Spacer()
Text("Confirm Donation Amount")
.font(.custom("Poppins-SemiBold", size: 13))
.foregroundColor(.black)
Spacer()
})
.frame(height:44)
.background(Color("#FFA919"))
.padding(.horizontal,20)
}
.shadow(color: .gray, radius: 1, x: 0, y: 2)
.cornerRadius(4)
}
.shadow(color: .gray, radius: 2, x: 0, y: 2)
.edgesIgnoringSafeArea(.all)
.frame(height:80)
.navigationBarBackButtonHidden(true)
.navigationBarTitle("Making Donation", displayMode: .inline)
}
}
func goBack(){
self.presentationMode.wrappedValue.dismiss()
}
}
Try this:
// Container Screen view for handling all screen
struct ContainerView: View {
#State var tabSelection: Screens = .screen1
var body: some View {
NavigationView{
TabView(selection: $tabSelection){
// Screen 1
// Hide tab bar view only for Screen 1
NavigationLink(destination: PushedView()){
VStack{
Text("Screen 1")
Text("Tap to PushedView")
}
}
.tabItem { Text("Screen 1") }
.tag(Screens.screen1)
// Screen 2
// same view using for screen 2 for directly shows on that
PushedView()
.tabItem { Text("Screen 2") }
.tag(Screens.screen2)
}
.navigationBarTitle( self.tabSelection.title)
}
}
}
// New view for pushing
struct PushedView: View {
var body: some View {
Text("Hi! This is the new View")
.navigationBarTitle("NewView")
}
}
// Tab screens
enum Screens{
case screen1, screen2
var title: String {
switch self {
case .screen1:
return "Screen1"
case .screen2:
return "Screen2"
}
}
}

SwiftUI animation affecting other views

Here is my View:
struct HomeView: View {
#ObservedObject var instance = Instance()
#State var dotColor = Color.gray
var repeatingAnimation: Animation {
Animation
.easeInOut(duration: 1.0)
.repeatForever()
}
var body: some View {
ZStack {
TabView(selection: $selectedTab) {
Settings(auth: auth)
.tabItem {
Image(systemName: "gear")
Text("Settings")
}.tag(0)
ZStack {
MapView(locationManager: locationManager)
Color.black.opacity(self.instance.status.opacity)
VStack {
Spacer()
Button(action: {
print("clicked")
withAnimation(self.repeatingAnimation) {
self.dotColor = Color.white
}
self.instance.changeStatus()
}){
HStack {
Text(self.instance.status.text)
.font(.system(size: 24))
.fontWeight(.bold)
.foregroundColor(Color.white)
Circle().frame(width: 12, height: 12)
.foregroundColor(self.dotColor)
.colorMultiply(self.dotColor)
.overlay(
Circle()
.stroke(Color.black, lineWidth: 1)
)
}
}.padding()
My animation, changes the color of the Circle() from gray to white every second. However, it also changes my ZStack color opacity (Color.black.opacity(self.instance.status.opacity)) every second - even though it's not related to my animation.
If I remove the animation this weird behaviour does not occur to the ZStack opacity.
How can I fix this?
You can disable animation of some modifier explicitly, like
Color.black.opacity(self.instance.status.opacity)
.animation(nil)

Buttons inside list item don't work properly

I have 2 views: PollCard and PollList (like list of polls)
In the PollCard view I have 2 buttons(images), that calls "answer" function:
HStack{
Button(action: {
self.answer()
print("Pressed first image")
}){
Image(poll.v1img)
.resizable()
.renderingMode(.original)
.scaledToFill()
.frame(width: 150, height: 200)
}.frame(width: 150, height: 200)
Button(action: { self.answer()}){
Image(poll.v2img )
.resizable()
.renderingMode(.original)
.frame(width: 150, height: 200)
}.frame(width: 150, height: 200).zIndex(4)
}
In the PollList view I have this simple list:
var body: some View {
HStack{
List(pollData) { poll in
PollCard(poll: poll)
}.padding()
}
}
But when I click the images in the list, it selects like all images and presses it
It is also very easy to check - terminal prints Pressed first image even if I've pressed only second image
What should I do to fix this?
As I mentioned in the comment section the workaround would be to substitute the HStack around the List with a ScrollView and the List with a ForEach:
struct ContentView: View {
struct Data: Identifiable {
var id: Int
}
#State var data = [Data(id: 0), Data(id: 1), Data(id: 2), Data(id: 3), Data(id: 4), Data(id: 5)]
var body: some View {
ScrollView {
ForEach(self.data) { data in
HStack {
Button(action: {
print("Pressed blue...")
}, label: {
Rectangle()
.foregroundColor(Color.blue)
.frame(width: 150, height: 200)
})
Button(action: {
print("Pressed red...")
}, label: {
Rectangle()
.foregroundColor(Color.red)
.frame(width: 150, height: 200)
})
}
}
}
}
}
I hope this helps!
this is an unexpected behavior of Button (or it is a bug?) in current SwiftUI (either on macOS or iOS).
The workaround in your case is simple, try to apply PlainButtonStyle for your buttons
import SwiftUI
struct ContentView: View {
var body: some View {
List {
Text("Hello, World!")
HStack {
Button(action: {
print("button1")
}) {
Color.yellow
}.buttonStyle(PlainButtonStyle())
Button(action: {
print("button2")
}) {
Color.green
}.buttonStyle(PlainButtonStyle())
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The funny thing is that it is enough to apply the style on one button only ... or apply it to parent HStack :-)
changing ContentView ...
struct ContentView: View {
var body: some View {
List {
Text("Hello, World!")
HStack {
Button(action: {
print("button1")
}) {
Color.yellow
}
Button(action: {
print("button2")
}) {
Color.green
}
}
.buttonStyle(PlainButtonStyle())
.frame(height: 100)
Text("By by, World!")
}
}
}
you get
where each of buttons works as expected

Resources