SwiftUI Circle View animation glitch - swiftui

import SwiftUI
struct CircularProgressView: View {
#Binding var progress: Float
private let strokeStyle = StrokeStyle(lineWidth: 30.0, lineCap: .round, lineJoin: .round)
private let rotation = Angle(degrees: 270.0)
var body: some View {
ZStack {
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: strokeStyle)
.opacity(0.2)
.foregroundColor(Color.black)
.rotationEffect(rotation)
.offset(x: 0, y: 10)
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: strokeStyle)
.opacity(1)
.foregroundColor(Color.white)
.rotationEffect(rotation)
}
.animation(.linear(), value: progress)
}
}
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
OtherView(progress: 0.6)
}
struct OtherView : View {
#State var progress : Float = 0.0
var body: some View {
ZStack {
Color.yellow
VStack {
CircularProgressView(progress: self.$progress)
.padding(50)
Button(action: {
if(progress >= 1) {
progress = 0
} else {
progress += 0.1
}
}) {
Text("try me")
.frame(width: 200, height: 50)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.blue, lineWidth: 2)
)
.padding(.bottom, 100)
}
}
}.ignoresSafeArea()
}
}
}
What could be the reason?

It is unclear definitely, probably due to undefined size of shapes as a nature... Anyway, seems using drawing group fixes the issue.
Here is a fixed code. Tested with Xcode 13 / iOS 15.
struct CircularProgressView: View {
#Binding var progress: Float
private let strokeStyle = StrokeStyle(lineWidth: 30.0, lineCap: .round, lineJoin: .round)
private let rotation = Angle(degrees: 270.0)
var body: some View {
ZStack {
Group {
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: strokeStyle)
.opacity(0.2)
.foregroundColor(Color.black)
.rotationEffect(rotation)
.offset(x: 0, y: 10)
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: strokeStyle)
.opacity(1)
.foregroundColor(Color.white)
.rotationEffect(rotation)
}
.padding() // << compensate offset within own bounds
}
.drawingGroup() // << here !!
.animation(.linear, value: progress)
}
}
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
OtherView(progress: 0.6)
}
struct OtherView : View {
#State var progress : Float = 0.0
var body: some View {
ZStack {
Color.yellow
VStack {
CircularProgressView(progress: self.$progress)
.padding()
Button(action: {
if(progress >= 1) {
progress = 0
} else {
progress += 0.1
}
}) {
Text("try me")
.frame(width: 200, height: 50)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.blue, lineWidth: 2)
)
.padding(.bottom, 100)
}
}
}.ignoresSafeArea()
}
}
}

Related

SwiftUI: Countdown Circle with Animation

I've created a progress circle for my App, that will count down from 28 to 0 (number of days until Payday)
I've got the circles to display how I'd like, but I want it to animate when I navigate to the view, is there a way to do this?
More a UI question, but do you think maybe an arrow within the circle to indicate the direction would look any good?
P.s - any ideas how to truncate all the zeros off my Double?
image of how this looks
Thanks!
ZStack {
let progressPeriod = Double(PayData.daysUntilPay) ?? 0
let progressPeriod2 = 1 - (progressPeriod / 28)
Circle()
.stroke(lineWidth: 30.0)
.opacity(0.3)
.foregroundColor(Color(red: 0.941, green: 0.426, blue: 0.004))
.frame(width:150)
.frame(height: 200)
Circle()
.trim(from: 0.0, to: progressPeriod2)
.stroke(style: StrokeStyle(lineWidth: 30.0, lineCap: .round, lineJoin: .round))
.foregroundColor(Color(red: 0.941, green: 0.426, blue: 0.004))
.rotationEffect(Angle(degrees: 270.0))
.frame(width:150)
.frame(height: 200)
Text("\(progressPeriod) days")
.bold()
}
Try to combine following code with your countdown:
struct ContentView: View {
#State var progressValue: Float = 0.0
var body: some View {
ZStack {
Color.yellow
.opacity(0.1)
.edgesIgnoringSafeArea(.all)
VStack {
ProgressBar(progress: self.$progressValue)
.frame(width: 150.0, height: 150.0)
.padding(40.0)
Button(action: {
self.incrementProgress()
}) {
HStack {
Image(systemName: "plus.rectangle.fill")
Text("Increment")
}
.padding(15.0)
.overlay(
RoundedRectangle(cornerRadius: 15.0)
.stroke(lineWidth: 2.0)
)
}
Spacer()
}
}
}
func incrementProgress() {
let randomValue = Float([0.012, 0.022, 0.034, 0.016, 0.11].randomElement()!)
self.progressValue += randomValue
}
}
struct ProgressBar: View {
#Binding var progress: Float
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.opacity(0.3)
.foregroundColor(Color.red)
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: StrokeStyle(lineWidth: 20.0, lineCap: .round, lineJoin: .round))
.foregroundColor(Color.red)
.rotationEffect(Angle(degrees: 270.0))
.animation(.linear)
Text(String(format: "%.0f %%", min(self.progress, 1.0)*100.0))
.font(.largeTitle)
.bold()
}
}
}

SwiftUI Custom Tab Bar icons not changing the tab. Area is above it

I am currently having trouble with my Custom Tab Bar there is a gray area above it (Tab View) that controls each tab but I need that to go under my custom tab bar but functionality of the TabView still be in effect and be used with the icons. You can hide the Tab bar with UITabBar.apperance() which gets rid of the gray area but no longer has any functions.. but I need that gray area to go under the tabs. If that makes sense?
Home.swift
import SwiftUI
struct Home: View {
//Hiding Tab Bar..
init() {
UITabBar.appearance().isHidden = false
}
var body: some View {
VStack(spacing: 0){
//Tab View...
TabView{
Color.blue
.tag("house.circle")
Color.green
.tag("pencil")
Color.pink
.tag("magnifyingglass")
Color.red
.tag("bell")
Color.yellow
.tag("cart")
}
//Custom Tab Bar...
CustomTabBar()
}
.ignoresSafeArea()
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
}
}
//Extending View To Get Screen Frame...
extension View {
func getRect()->CGRect {
return UIScreen.main.bounds
}
}
CustomTabBar.swift
import SwiftUI
struct CustomTabBar: View {
var body: some View {
HStack(spacing: 0){
// Tab Bar Button...
TabBarButton(systemName: "house.circle")
.background(Color.blue)
TabBarButton(systemName: "pencil")
.background(Color.green)
Button(action: {}, label: {
Image(systemName: "magnifyingglass")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width:24, height:24)
.foregroundColor(.white)
.padding(20)
.background(Color.green)
.clipShape(Circle())
//Shadows
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 5, y: 5)
.shadow(color: Color.black.opacity(0.05), radius: 5, x: -5, y: -5)
})
.tag("magnifyingglass")
TabBarButton(systemName: "bell")
.background(Color.red)
TabBarButton(systemName: "cart")
.background(Color.yellow)
}
.padding(.top)
//Decreasing the extra padding added...
.padding(.vertical, -0)
.padding(.bottom,getSafeArea().bottom == 0 ? 15 : getSafeArea().bottom)
.background(Color.white)
}
}
struct CustomTabBar_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
}
}
}
//extending view to get safe area...
extension View {
func getSafeArea()-> UIEdgeInsets {
return UIApplication.shared.windows.first?.safeAreaInsets ?? UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
struct TabBarButton: View {
var systemName: String
var body: some View{
Button(action: {
}, label: {
VStack(spacing: 8){
Image(systemName)
.resizable()
//Since its asset image...
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width:28, height: 28)
}
.frame(maxWidth: .infinity)
})
}
}
EDIT: SECOND IMAGE I am hiding the tab bar setting it to true instead of false.
//Hiding Tab Bar..
init() {
UITabBar.appearance().isHidden = true
}
you could try this to "cover" the original TabView bar:
In Home replace VStack with ZStack.
and
struct CustomTabBar: View {
var body: some View {
VStack (alignment: .leading) {
Spacer()
HStack(spacing: 0) {
TabBarButton(systemName: "house.circle").background(Color.blue)
TabBarButton(systemName: "pencil").background(Color.green)
Button(action: {}, label: {
Image(systemName: "magnifyingglass")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width:24, height:24)
.foregroundColor(.white)
.padding(20)
.background(Color.green)
.clipShape(Circle())
//Shadows
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 5, y: 5)
.shadow(color: Color.black.opacity(0.05), radius: 5, x: -5, y: -5)
})
.tag("magnifyingglass")
TabBarButton(systemName: "bell").background(Color.red)
TabBarButton(systemName: "cart").background(Color.yellow)
}
}
.padding(.bottom, getSafeArea().bottom == 0 ? 15 : getSafeArea().bottom)
.background(Color.white)
}
}
you will then need to implement the action of each of your CustomTabBar buttons.
EDIT1:
ok, as I mentioned you need to implement the actions for your buttons.
There are many ways to do this, this is just one approach:
struct CustomTabBar: View {
#Binding var tagSelect: String
var body: some View {
VStack (alignment: .leading) {
Spacer()
HStack(spacing: 0) {
TabBarButton(tagSelect: $tagSelect, systemName: "house.circle").background(Color.blue)
TabBarButton(tagSelect: $tagSelect, systemName: "pencil").background(Color.green)
Button(action: {}, label: {
Image(systemName: "magnifyingglass")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width:24, height:24)
.foregroundColor(.white)
.padding(20)
.background(Color.green)
.clipShape(Circle())
//Shadows
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 5, y: 5)
.shadow(color: Color.black.opacity(0.05), radius: 5, x: -5, y: -5)
})
.tag("magnifyingglass")
TabBarButton(tagSelect: $tagSelect, systemName: "bell").background(Color.red)
TabBarButton(tagSelect: $tagSelect, systemName: "cart").background(Color.yellow)
}
}
.padding(.bottom,getSafeArea().bottom == 0 ? 15 : getSafeArea().bottom)
// no background or use opacity, like this
.background(Color.white.opacity(0.01)) // <-- important
}
}
extension View {
func getSafeArea()-> UIEdgeInsets {
return UIApplication.shared.windows.first?.safeAreaInsets ?? UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
struct TabBarButton: View {
#Binding var tagSelect: String
var systemName: String
var body: some View{
Button(action: {tagSelect = systemName }, label: {
VStack(spacing: 8){
Image(systemName)
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width:28, height: 28)
}
.frame(maxWidth: .infinity)
})
}
}
struct Home: View {
#State var tagSelect = "house.circle"
init() {
UITabBar.appearance().isHidden = false
}
var body: some View {
ZStack {
TabView (selection: $tagSelect) {
Color.blue.tag("house.circle")
Color.green.tag("pencil")
Color.pink.tag("magnifyingglass")
Color.red.tag("bell")
Color.yellow.tag("cart")
}
CustomTabBar(tagSelect: $tagSelect)
}
.ignoresSafeArea()
}
}
extension View {
func getRect()->CGRect {
return UIScreen.main.bounds
}
}

New to SwiftUI trying to understand layout changes without (I guess it doesn't have ) Layout constraints

The problem is I don't understand how to fix my Content View to the screen and handle things like rotation or even how to make other screen sizes dynamic. Below is my entire View code. When I create a new app out of the box it looks fine in preview, the way I like it. However, screen sizes change and all I know to do is put magic numbers, because there is no superview or frame to size from. Also I can't seem to clip at screen edges. So my background view is way to big. How is this handled in SwiftUI?
import SwiftUI
import AuthenticationServices
struct LoginScreen: View {
var body: some View {
ZStack{
backgroundLayout
loginLayout
}
}
var loginLayout: some View {
VStack {
welcomeText
signInWithAppleButton
}
}
var backgroundLayout: some View {
ZStack{
Rectangle()
.foregroundColor(.init(.sRGB, red: 0, green: 0.750, blue: 0.750, opacity: 0.25))
Circle()
.foregroundColor(.blue)
.offset(x: 200, y: -200)
.aspectRatio(1/3, contentMode: .fill)
.clipped()
Circle()
.foregroundColor(.green)
.offset(x: -200, y: 200)
.aspectRatio(1/3, contentMode: .fill)
.clipped()
}
}
var welcomeText: some View {
HStack {
VStack {
Text("Sign Up")
.font(.largeTitle)
.bold()
Text("Sign In")
.font(.title)
.bold()
Text("Get Started!")
.font(.title2)
.bold()
}
.offset(x: -100, y: -150)
}
}
var signInWithAppleButton: some View {
SignInWithAppleButton(
.continue,
onRequest: { request in
request.requestedScopes = [.fullName, .email]
},
onCompletion: { result in
switch result {
case .success (let authenticationResults):
print("Authorization successful! :\(authenticationResults)")
case .failure(let error):
print("Authorization failed: " + error.localizedDescription)
}
}
)
.offset(x: 0, y: 150)
.frame(width: 200, height: 50, alignment: .center)
}
}
struct Login_Preview: PreviewProvider {
static var previews: some View {
LoginScreen()
}
}
After trying several things this is all I could come up with. It's not perfect and certainly not ideal, but at least it work somewhat as expected.
import SwiftUI
import AuthenticationServices
struct LoginScreen: View {
var body: some View {
loginLayout.background(backgroundLayout)
.statusBar(hidden: true)
}
var loginLayout: some View {
GeometryReader {geo in
VStack {
welcomeText.padding(.top, geo.size.height / 10)
Spacer()
signInWithAppleButton
.padding(.bottom, geo.size.height / 10)
}
}
}
var backgroundLayout: some View {
GeometryReader { geo in
let circleSize = min(geo.size.width, geo.size.height)
ZStack {
Rectangle()
.foregroundColor(.init(.sRGB, red: 0, green: 0.750, blue: 0.750, opacity: 0.25))
Circle()
.foregroundColor(.blue)
.offset(x: circleSize / 1.75,
y: -geo.size.height / 2.5)
.frame(width: circleSize, height: circleSize, alignment: .center)
Circle()
.foregroundColor(.green)
.offset(x: -circleSize / 1.75, y: geo.size.height / 2.5)
.frame(width: circleSize, height: circleSize, alignment: .center)
}
.frame(width: geo.size.width, height: geo.size.height, alignment: .top)
}
}
var welcomeText: some View {
GeometryReader { geo in
HStack {
VStack {
Text("Sign Up")
.font(.largeTitle)
.bold()
Text("Sign In")
.font(.title)
.bold()
Text("Get Started!")
.font(.title2)
.bold()
}
.padding(.leading, geo.size.width / 8)
Spacer()
}
}
}
var signInWithAppleButton: some View {
SignInWithAppleButton(
// Add this below for Continue with Apple button
.continue,
onRequest: { request in
request.requestedScopes = [.fullName, .email]
},
onCompletion: { result in
switch result {
case .success (let authenticationResults):
print("Authorization successful! :\(authenticationResults)")
case .failure(let error):
print("Authorization failed: " + error.localizedDescription)
}
}
)
.frame(width: 200, height: 50, alignment: .center)
}
}
struct Login_Preview: PreviewProvider {
static var previews: some View {
LoginScreen()
}
}

Why the text appear at loading with animation?

I want to hide some text behind the NavigationBarTitle and show it when user press a button, it's working fine. The only problem is with the animation. At loading we see the text moving behind the NavigationBarTitle and that's not what i want.
How can i fix this?
I tried with offset and position but it's not working...
Code :
import SwiftUI
struct TestZStackNavigationView: View {
#State var isShowed = false
let screenSize: CGRect = UIScreen.main.bounds
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 0){
Text("Im the hidden text")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(width: screenSize.width, height: 40, alignment: .center)
.background(Color.red)
// .offset(y: self.isShowed ? -315 : -355)
.position(x: screenSize.width / 2, y: self.isShowed ? 20 : -40)
.animation(.easeIn(duration: 0.5))
Button(action: {
self.isShowed.toggle()
}) {
Text("click me")
}
.navigationBarTitle(Text("Navigation Bar Title"), displayMode:.inline)
}
}
}
}
struct TestZStackNavigationView_Previews: PreviewProvider {
static var previews: some View {
TestZStackNavigationView()
}
}
There are two possibilities
make animation per-state (and no other changes)
.animation(.easeIn(duration: 0.5), value: isShowed)
replace implicit animation as modifier and add explicit animation in action
.position(x: screenSize.width / 2, y: self.isShowed ? 20 : -40)
// .animation(.easeIn(duration: 0.5)) // remove from here
Button(action: {
withAnimation(Animation.easeIn(duration: 0.5)) { // << add this
self.isShowed.toggle()
}
}) {
Text("click me")
}
I want to use it now with an ObservedObject but i got that same behavior as first code. Why?
struct TestZStackNavigationView: View {
// #State var isShowed = false
let screenSize: CGRect = UIScreen.main.bounds
#ObservedObject var online = NetStatus()
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 0){
Text("Im the hidden text")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(width: screenSize.width, height: 40, alignment: .center)
.background(Color.red)
.position(x: screenSize.width / 2, y: online.connected ? -40 : 20)
.animation(.easeIn(duration: 0.5), value: online.connected)
.navigationBarTitle(Text("Navigation Bar Title"), displayMode:.inline)
}
}
}
}

Pass to Another View after Success View Appears

After seeing the popup view that says your credit card saved successfully. I want to see this popup for 2-3 seconds then to pass another view called AddressView. Maybe it is irrelevant but I also added that popup view and the name is SuccessCardView.
#State private(set) var successAlert = false
ZStack {
HStack {
Button(action: {
self.isListTapped.toggle()
}, label: { CustomButton(title: "Listeden Sec", icon: .none, status: .enable, width: 150)})
Button(action: {
self.isSaved.toggle()
self.creditCards.append(self.creditCard)
print(self.creditCards[0].cardNumber)
if self.creditCards[0].cardNumber == "" {
self.showingAlert = true
} else if self.creditCards[0].cardNumber.count == 16 {
self.successAlert = true
}
}, label: { CustomButton(title: "Kaydet", icon: .none, status: .enable, width: 150)})
.alert(isPresented: $showingAlert) {
Alert(title: Text("Kart Bilgileri Hatali"), message: Text("Tekrar Kontrol Edin"), dismissButton: .default(Text("Got it!")))
}
}
SuccessCardView(isShown: $successAlert) // I want to show that view than jump to another view
}
struct SuccessCardView: View {
#Binding var isShown: Bool
#State var viewState = CGSize.zero
var body: some View {
VStack {
ZStack {
Rectangle()
.foregroundColor(Color(#colorLiteral(red: 0, green: 0.6588235294, blue: 0.5254901961, alpha: 1)))
.cornerRadius(10)
.frame(width: 355, height: 76)
HStack {
VStack(alignment: .leading) {
Text("Tebrikler!")
.font(Font.custom("SFCompactDisplay-Bold", size: 16))
.foregroundColor(.white)
Text("Kart Basariyla Eklendi")
.font(Font.custom("SFCompactDisplay", size: 14))
.foregroundColor(.white)
}
.offset(x: -70)
}
}
Spacer()
}
.offset(y: isShown ? .zero : -UIScreen.main.bounds.size.height)
.offset(y: viewState.height )
.animation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0))
.offset(y: -100)
}
}
It is not clear where/how is AddressView configured, but you can do the following
} else if self.creditCards[0].cardNumber.count == 16 {
self.successAlert = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.successAlert = false // hide popup
self.showAddressView = true // eg. activate navigation link
}
}