I wanted to make a bottomsheet in SwiftUI with my own efforts, I open it using animation, but my animation doesn't work when closing, what is the reason?
I wonder if the offset value is increasing with animation, is there a problem while it is decreasing I am not very good at SwiftUI so I could not fully understand the problem.
struct ContentView: View {
#State var isOpen = false
#State var offset = UIScreen.main.bounds.height / 3
var body: some View {
ZStack {
Color.blue
.ignoresSafeArea()
Button(action: {
self.isOpen.toggle()
}, label: {
ZStack {
RoundedRectangle(cornerRadius: 25.0)
.foregroundColor(.black)
Text("Open")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
}
})
.buttonStyle(DefaultButtonStyle())
.frame(width: 300, height: 50, alignment: .center)
if isOpen {
GeometryReader { geometry in
VStack {
Spacer()
BottomSheet()
.frame(width: geometry.size.width,
height: geometry.size.height / 3,
alignment: .center)
.background(
Color.white
)
.offset(y: offset)
.onAppear(perform: {
withAnimation {
self.offset = 0
}
})
.onDisappear(perform: {
withAnimation {
self.offset = UIScreen.main.bounds.height / 3
}
})
}.ignoresSafeArea()
}
}
}
}
}
BottomSheet
struct BottomSheet: View {
var body: some View {
Text("Hello, World!")
}
}
onDisappear gets called when the view was removed, that's the reason custom animation not working :
struct ContentView: View {
#State var isOpen = false
var offset: CGFloat {
isOpen ? 0 : UIScreen.main.bounds.height / 3
}
var body: some View {
ZStack {
Color.blue
.ignoresSafeArea()
Button(action: {
self.isOpen.toggle()
}, label: {
ZStack {
RoundedRectangle(cornerRadius: 25.0)
.foregroundColor(.black)
Text("Open")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
}
})
.buttonStyle(DefaultButtonStyle())
.frame(width: 300, height: 50, alignment: .center)
GeometryReader { geometry in
VStack {
Spacer()
BottomSheet()
.frame(width:geometry.size.width,
height: geometry.size.height / 3,
alignment: .center)
.background(
Color.white
)
.offset(y: offset)
.animation(.easeInOut(duration: 0.5)) .transition(.move(edge: .bottom))
} .edgesIgnoringSafeArea(.bottom)
}
}
}
}
Related
how to make is that when i press the burger menu it doesn't move the other content around it
i am using a button with an if statement in a ZStack to create a menu like effect from the side
import SwiftUI
struct ContentView: View {
#State private var showMenu = false
var body: some View {
VStack {
HStack {
Image("logo")
.resizable()
.frame(width: 164, height: 34)
.padding(15)
Spacer()
ZStack {
if showMenu{
Text("About")
.foregroundColor(.white)
.frame(width: UIScreen.main.bounds.width/2)
}
Button {
showMenu.toggle()
}label: {
Image("menu")
.resizable()
.frame(width: 30, height: 30)
.padding(15)
}
}
.background(Color.black.opacity(showMenu ? 0.7 : 0))
.animation(.default)
.edgesIgnoringSafeArea(.all)
.onTapGesture {
showMenu = false
}
}
VStack {
Text("Hire The World's Top Calibers")
.font(.system(size: 47))
.fontWeight(.semibold)
Image("image")
.resizable()
.frame(width: 500, height: 400)
Spacer()
HStack {
Button {
//code
}label: {
Rectangle()
.fill(.black)
.frame(width: 150, height: 70)
.overlay(
Text("Hire a Caliber")
.foregroundColor(.white)
)
}
.padding()
Button {
//code
}label: {
Rectangle()
.fill(.white)
.border(Color.black, width: 2)
.frame(width: 150, height: 70)
.overlay(
Text("Join as a Caliber")
.foregroundColor(.black)
)
}
.padding()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.preferredColorScheme(.light)
}
}
it was originally in a HStack and i tried putting it in a ZStack but that didn't work
i tried looking for answers on chatGPT and this is the best that i got
I'm making a swiftui app, I have a NavigationView that contains a VStack and a List inside.
I've tried to put the following line of code in a lot of places .background(Color.blue) but it had no effect anywhere (nothing happened).
How can I set a background for the view itself?
I know it’s a very simple thing, but it doesn’t work out at all...
This is my code:
struct ContentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Todo.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Todo.name, ascending: true)]) var todos: FetchedResults<Todo>
#State private var showingAddTodoView: Bool = false
#State private var animatingButton: Bool = false
var body: some View {
NavigationView {
ZStack {
List {
ForEach(self.todos, id: \.self) { todo in
HStack {
Circle()
.frame(width: 12, height: 12, alignment: .center)
.foregroundColor(self.colorize(priority: todo.priority ?? "Normal"))
Text(todo.name ?? "Unknown")
.fontWeight(.semibold)
Spacer()
Text(todo.priority ?? "Unkown")
.font(.footnote)
.foregroundColor(Color(UIColor.systemGray2))
.padding(3)
.frame(minWidth: 62)
.overlay(
Capsule().stroke(Color(self.colorize(priority: todo.priority ?? "Normal")), lineWidth: 0.75)
)
Spacer()
Image(systemName: todo.completed ? "checkmark.square": "square")
.foregroundColor(todo.completed ? .green : .black)
.onTapGesture {
self.updateTodo(todo)
}
}
.padding(.vertical, 10)
}
.onDelete(perform: deleteTodo)
}
.navigationBarTitle("Todos", displayMode: .inline)
.navigationBarItems(
leading: EditButton(),
trailing:
Button(action: {
self.showingAddTodoView.toggle()
}) {
Image(systemName: "plus")
}
.sheet(isPresented: $showingAddTodoView) {
AddTodoView().environment(\.managedObjectContext, self.managedObjectContext)
}
)
if todos.count == 0 {
NoTodosView()
}
}
.sheet(isPresented: $showingAddTodoView) {
AddTodoView().environment(\.managedObjectContext, self.managedObjectContext)
}
.overlay(
ZStack {
Group {
Circle()
.fill(Color.blue)
.opacity(self.animatingButton ? 0.2 : 0)
.scaleEffect(self.animatingButton ? 1 : 0)
.frame(width: 68, height: 68, alignment: .center)
Circle()
.fill(Color.blue)
.opacity(self.animatingButton ? 0.15 : 0)
.scaleEffect(self.animatingButton ? 1 : 0)
.frame(width: 88, height: 88, alignment: .center)
}
.animation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true))
Button(action: {
self.showingAddTodoView.toggle()
}) {
Image(systemName: "plus.circle.fill")
.resizable()
.scaledToFit()
.background(Circle().fill(Color("Color")))
.frame(width: 48, height: 48, alignment: .center)
}
.onAppear(perform: {
self.animatingButton.toggle()
})
}
.padding(.bottom, 15)
.padding(.trailing, 15)
, alignment: .bottomTrailing
)
}
}
I want to set the background color for this view:
try this...
struct ContentView: View {
var body: some View {
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().backgroundColor = .clear
return NavigationView {
ZStack {
Color.blue
.edgesIgnoringSafeArea(.all)
List{
ForEach((1...5), id: \.self){_ in
HStack{
Text("item")
}
}
// .listRowBackground(Color.blue)
}
}
}
}
}
I have a simple loading view on SwiftUI.
When I am displaying this loading screen with .navigationBarHidden(true) on NavigationView.
There is an issue that animation has an unwanted effect on it.
This is my loading animation
struct LoaderThreeDot: View {
var size: CGFloat = 20
#State private var shouldAnimate = false
var body: some View {
HStack(alignment: .center) {
Circle()
.fill(Color.blue)
.scaleEffect(shouldAnimate ? 1.0 : 0.5, anchor: .center)
.animation(Animation.easeInOut(duration: 0.5).repeatForever())
.frame(width: size, height: size)
Circle()
.fill(Color.blue)
.scaleEffect(shouldAnimate ? 1.0 : 0.5, anchor: .center)
.animation(Animation.easeInOut(duration: 0.5).repeatForever().delay(0.3))
.frame(width: size, height: size, alignment: .center)
Circle()
.fill(Color.blue)
.scaleEffect(shouldAnimate ? 1.0 : 0.5, anchor: .center)
.animation(Animation.easeInOut(duration: 0.5).repeatForever().delay(0.6))
.frame(width: size, height: size, alignment: .center)
}
.onAppear {
self.shouldAnimate = true
}
}
}
LoadingView as follow:
struct LoadingView<Content>: View where Content: View {
let title: String
var content: () -> Content
#State var showLoader = false
var body: some View {
ZStack {
self.content()
.disabled(true)
.blur(radius: 3)
Rectangle()
.foregroundColor(Color.black.opacity(0.4))
.ignoresSafeArea()
VStack {
if showLoader {
LoaderThreeDot()
}
Text(title)
.foregroundColor(.black)
.font(.body)
.padding(.top, 10)
}
.padding(.all, 60)
.background(backgroundView)
}
.onAppear {
showLoader.toggle()
}
}
private var backgroundView: some View {
RoundedRectangle(cornerRadius: 12)
.foregroundColor(Color.white)
.shadow(radius: 10)
}
}
And simply presenting it as follow:
NavigationView {
ZStack {
LoadingView(title: "Loading...") {
Rectangle()
.foregroundColor(.red)
}
}
.navigationBarHidden(true)
}
If I remove .navigationBarHidden(true) animation looks ok.
So I am guessing that the animation effect started when the navigation bar was shown and it somehow affecting the animation after the navigation bar is hidden.
Is there any way I can avoid this?
Change your toggle on the main thered.
// Other code
.onAppear() {
DispatchQueue.main.async { //<--- Here
showLoader.toggle()
}
}
// Other code
I created a simple collection with navigation from the last page to the next screen.
How to correctly write transition condition if I need to apply the .fullScreenCover modifier to the button on the last page array index?
How do I correctly place the background image on the first collection screen so that it is not on the following screens, by convention, if the index is the first?
import SwiftUI
struct IntroView: View {
#ObservedObject var viewModel = IntroViewModel()
#State private var tabSelection = 0
#State private var isLastPage = false
var body: some View {
ZStack {
TabView(selection: $tabSelection) {
ForEach(0..<viewModel.pages.endIndex) { index in
VStack {
Image("icnDE")
.padding(.bottom, 20)
.padding(.top, 50)
.frame(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
Text(viewModel.pages[index].name)
.font(Font.custom("TeXGyreAdventor-Bold", size: 32))
.foregroundColor(.white)
.multilineTextAlignment(.center)
.padding(.horizontal,30)
Button(action: {
self.tabSelection += 1
self.isLastPage = false
}) {
Text(viewModel.pages[index].buttonName)
.font(Font.custom("TeXGyreAdventor-Bold", size: 18))
.frame(width: 335, height: 56, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(12)
.padding(.top, 50)
}
if tabSelection == viewModel.pages.count - 1, isLastPage == true {
Button(action: {
self.tabSelection += 1
self.isLastPage = false
}) {
Text(viewModel.pages[index].buttonName)
.font(Font.custom("TeXGyreAdventor-Bold", size: 18))
.frame(width: 335, height: 56, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(12)
.padding(.top, 50)
}.fullScreenCover(isPresented: $isLastPage, content: {
LoginView()})
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle.init(indexDisplayMode: .never))
.edgesIgnoringSafeArea(.all)
}
.background(
Image("imgHappypeople")
.resizable()
.edgesIgnoringSafeArea(.all)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
}
}
struct Intro_Previews: PreviewProvider {
static var previews: some View {
IntroView()
.previewDevice("iPhone 11")
}
}
For open full screen cover, Add .fullScreenCover at the top of the ZStack and no need to add a condition for two-button.
struct IntroView: View {
#ObservedObject var viewModel = IntroViewModel()
#State private var tabSelection = 0
#State private var isLastPage = false
var body: some View {
ZStack {
TabView(selection: $tabSelection) {
ForEach(0..<viewModel.pages.endIndex) { index in
VStack {
Image("icnDE")
.padding(.bottom, 20)
.padding(.top, 50)
.frame(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
Text(viewModel.pages[index].name)
.font(Font.custom("TeXGyreAdventor-Bold", size: 32))
.foregroundColor(.white)
.multilineTextAlignment(.center)
.padding(.horizontal,30)
Button(action: {
if self.tabSelection == viewModel.pages.count - 1 {
self.isLastPage = true
} else {
self.tabSelection += 1
} //<-- Use this condition
}) {
Text("\(index)")
.font(Font.custom("TeXGyreAdventor-Bold", size: 18))
.frame(width: 335, height: 56, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(12)
.padding(.top, 50)
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle.init(indexDisplayMode: .never))
.edgesIgnoringSafeArea(.all)
}
.fullScreenCover(isPresented: $isLastPage, content: {
Text("Details")}) //<== Use fullScreenCover here
.background(
Image("imgHappypeople")
.resizable()
.edgesIgnoringSafeArea(.all)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
}
}
I'm developing a iOS app relies on SwiftUI.
I have a ZStack view and inside it, I call a another view along with a button.
ZStack(alignment: .bottomTrailing) {
ImageStepView(data: self.data[randomImageNum])
Button(action: { self.showFavorites = true }) {
HStack {
Image(systemName: "suit.heart.fill")
Text("FAVORITES")
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 15)
.foregroundColor(.white)
.padding()
.background(Color.black.opacity(0.6))
.cornerRadius(5)
.padding(.horizontal, 20)
}
}
ImageStepView.swift
struct ImageStepView: View {
var data: ImageDataModel
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
VStack{
Image(data.image)
.resizable()
.border(Color.white, width: 5)
.overlay(
Rectangle().stroke(Color.white, lineWidth: 4))
.shadow(color: Color.gray, radius: 10, x: 10, y: 10)
.scaledToFit()
.frame(height: geometry.size.height-110)
} .padding()
VStack{
VStack(alignment: .leading) {
HStack{...}
HStack {...}
}
Spacer()
.frame(height: 50)
VStack(alignment: .leading){
VStack{
HStack {...}
HStack {...}
HStack {...}
}
}
}.padding()
}.background(Color("Color").ignoresSafeArea(.all))
.frame(width: geometry.size.width)
.frame(minHeight: geometry.size.height)
}
}
}
ImageStepView has a ScroolView, that's why Button not appears on the end of ScroolView, it appears on bottom of the screen.
What I want is to show The Button not on bottom of the screen but end of the ImageStepView.
You can make the ImageStepView accept a generic parameter - a view to be injected:
struct ImageStepView<Injected: View>: View {
var data: ImageDataModel
var injectedView: () -> Injected
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
// ScrollView contents
injectedView()
}
.background(Color("Color").ignoresSafeArea(.all))
.frame(width: geometry.size.width)
.frame(minHeight: geometry.size.height)
}
}
}
and pass the injected view to the ImageStepView:
ImageStepView(data: self.data[randomImageNum]) {
Button(action: { self.showFavorites = true }) { ... }
}
or
ImageStepView(data: self.data[randomImageNum]) { EmptyView() }