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
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 want to lay a View over another when I tap a button.
The following is my code:
import SwiftUI
struct SheetView: View {
#State private var showSheet: Bool = false
var body: some View {
NavigationView {
VStack {
ZStack {
Rectangle()
.fill(Color.orange)
.frame(height: 32.0)
Button("Please select a mailing address") {
showSheet.toggle()
}.foregroundColor(Color.black)
}
Spacer()
}
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(StackNavigationViewStyle())
}
.overlay(popOver)
}
var popOver: some View {
Group {
if showSheet {
ZStack {
Color.black.opacity(0.4).ignoresSafeArea()
ZStack {
Rectangle()
.fill(Color.white)
//.frame(width: UIScreen.main.bounds.width, height: 400)
.frame(maxWidth: .infinity, maxHeight: 320.0, alignment: .bottom)
//.position(x: UIScreen.main.bounds.width / 2.0, y: 600)
}
}.onTapGesture {
showSheet.toggle()
}
}
}
}
}
And it looks like the following picture. I get pretty much what I need except that the overlaid View will appear at the center. How can I make it appear, aligning to the bottom View?
Here is a fix - use alignment for internal ZStack (tested with Xcode 13.2 / iOS 15.2):
ZStack(alignment: .bottom) {
Color.black.opacity(0.4).ignoresSafeArea()
ZStack {
Rectangle()
.fill(Color.white)
.frame(maxWidth: .infinity, maxHeight: 320.0, alignment: .bottom)
}
}
//.ignoresSafeArea() // << probably you also need this
.onTapGesture {
showSheet.toggle()
}
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)
}
}
}
}
I am complete beginner with SwiftUI and I can't wrap my head around how to connect these images with views that represents lines. Now I simply have 3 VStacks with image and text and put them into a HStack, but don't know how to connect these images with a line shown in red in the picture I attached. Note that there's some space between the line and the image. I need general direction and some hints, full working code not necessary.
Thank you.
How's this?
In SwiftUI, you use HStacks and VStacks to stack your Views. For the red line, a Rectangle should do. Here's the code:
struct ContentView: View {
var body: some View {
HStack { /// horizontal stack
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
Rectangle()
.fill(Color.red)
.frame(height: 5)
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
Rectangle()
.fill(Color.red)
.frame(height: 5)
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.fixed(width: 800, height: 200))
}
}
Version 1.0.0
I decided to give my answer which is same like aheze answer with this difference that you can have CustomVerticalAlignment as well! As I see in your Image in question you want that also:
with CustomVerticalAlignment: In center!
without CustomVerticalAlignment: off center!
import SwiftUI
struct ContentView: View {
var body: some View {
HStack(alignment: .customVerticalAlignment) {
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
Capsule()
.fill(Color.red)
.frame(height: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
Capsule()
.fill(Color.red)
.frame(height: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
}
.padding()
}
}
extension VerticalAlignment {
struct CustomVerticalAlignment: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
d[VerticalAlignment.center]
}
}
static let customVerticalAlignment = VerticalAlignment(CustomVerticalAlignment.self)
}
Update Version 2.0.0
About this version: I would say it does the same job of version 1.0.0 in less code and also Text and Line are not depending on VStack or eachother any moere!
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
image.overlay(text.offset(y: 40), alignment: .bottom)
capsule
image.overlay(text.offset(y: 40), alignment: .bottom)
capsule
image.overlay(text.offset(y: 40), alignment: .bottom)
}
.padding(50)
}
var image: some View {
return Image(systemName: "star.fill")
.resizable()
.scaledToFit()
.padding(10)
.shadow(radius: 10)
.frame(width: 50, height: 50, alignment: .center)
.foregroundColor(Color.red)
.background(Color.yellow)
.border(Color.black, width: 5)
}
var capsule: some View {
return Capsule()
.fill(Color.red)
.frame(height: 5)
}
var text: some View {
return Text("Hello World!")
.lineLimit(1)
.fixedSize()
}
}
You could define a Shape that represents your line.
I used the spacing parameter of HStack to do the spacing:
struct MyLine : Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: 0, y: rect.midY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
}
}
}
struct ContentView: View {
var body: some View {
HStack(spacing: 10) {
VStack {
Image(systemName: "pencil")
Text("Label")
}
MyLine().stroke(Color.red)
VStack {
Image(systemName: "pencil")
Text("Label 2")
}
MyLine().stroke(Color.red)
VStack {
Image(systemName: "pencil")
Text("Label 3")
}
}
}
}
You could add a lineWidth parameter to make the stroke thicker:
.stroke(Color.red, lineWidth: 4)
Also, if you didn't using spacing on the HStack, you could using a padding modifier on either the VStacks or the MyLines to get the spacing.
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() }