I could not rotate the green part in the circular path.
I want to set dynamically infinite time.
I created a struct for ProgressView and want to set it as ProgressView in my project.
how to implement the red and black colour curve design in the rotation
path
struct CircularProgressView: View {
#State var progressValue: Float = 0.0
var body: some View {
ZStack {
ProgressBar(progress: self.$progressValue)
.frame(width: 150.0, height: 150.0)
.padding(40.0)
}.onAppear{
self.incrementProgress()
}
}
func incrementProgress() {
let randomValue = Float([0.012, 0.022, 0.034, 0.016, 0.11, 0.012].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, 2.0)))
.stroke(style: StrokeStyle(lineWidth: 20.0, lineCap: .round, lineJoin: .round))
.foregroundColor(Color.green)
.rotationEffect(Angle(degrees: 270))
.animation(.linear)
}
}
}
This is the Preview struct.
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
CircularProgressView()
}
}
Thanks in advance for helping me.
struct CircularProgressView: View {
#State var progressValue: Float = 0.0
#State private var isLoaderVisible: Bool = false
var degree = 90
var body: some View {
ZStack {
ProgressBar(progress: self.$progressValue)
.frame(width: 150.0, height: 150.0)
.padding(40.0)
}.overlay(LoaderView(showLoader: isLoaderVisible))
.onAppear {
isLoaderVisible.toggle()
}
}
}
struct ProgressBar: View {
#Binding var progress: Float
#State var degree:Double = 90
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.foregroundColor(Color.green)
}
}
}
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
CircularProgressView()
}
}
// enum for LoaderView
public enum LoaderAnimation {
case low, medium, high
var animationSpeed: Double {
switch self {
case .low: return 1.0
case .medium: return 0.8
case .high: return 10.2
}
}
}
This this the LoaderView which we set on overlay and animate
public struct LoaderView: View {
var loaderAnimationSpeed: LoaderAnimation? = .high
var showLoader: Bool = false
public var body: some View {
GeometryReader { reader in
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.opacity(0.3)
.foregroundColor(Color.yellow)
Circle()
.trim(from: 0.6, to: 1)
.stroke(.green, lineWidth: 20)
.rotationEffect(.degrees(showLoader ? 360 : 0))
.animation(Animation.easeInOut(duration: showLoader ? loaderAnimationSpeed?.animationSpeed ?? 0.0 : 1).repeatForever(autoreverses: false), value: showLoader)
}
}
}
public init( loaderAnimationSpeed: LoaderAnimation? = .medium, showLoader: Bool) {
self.loaderAnimationSpeed = loaderAnimationSpeed
self.showLoader = showLoader
}
}
Related
I have a structure, where the parent view is a hidden pop-up with content. On some user action parent View starts to slide up. I need all its content to slide up with the parent View. It was working perfectly before iOS 16, but now it's broken. If the child's View subview #State is changed during the animation, then this View appears instantly on a screen, i.e. not sliding. As I understand, because View's #State was changed SwiftUI redraws this particular View and disables its animation, so it appears in its final position without animation. I was trying to force the animation of this particular View using .animation or withAnimation. Nothing of it helped. How to fix this bug in iOS 16?
Minimal reproducible example:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import SwiftUI
struct ContentView: View {
#State var shouldShow: SlideCardPosition = .bottom
#State var text1: String = ""
var body: some View {
VStack {
Button {
shouldShow = .top
} label: {
Text("Show PopUp")
.padding(.top, 100)
}
PopUpUIView(shouldShow: $shouldShow)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
struct SlideOverCard<Content: View>: View {
#GestureState private var dragState = SlideDragState.inactive
#Binding private var position: SlideCardPosition
#State private var highlightedBackground = false
private var contentHeight: CGFloat
private var backgroundColor: Color?
private var withCorners: Bool
private var isHandleHidden: Bool
private var overlayOpacity: CGFloat
init(position: Binding<SlideCardPosition>,
contentHeight: CGFloat,
backgroundColor: Color? = nil,
withCorners: Bool = true,
isHandleHidden: Bool = false,
overlayOpacity: CGFloat = 0.75,
content: #escaping () -> Content) {
_position = position
self.content = content
self.contentHeight = contentHeight
self.backgroundColor = backgroundColor
self.withCorners = withCorners
self.isHandleHidden = isHandleHidden
self.overlayOpacity = overlayOpacity
}
var content: () -> Content
var body: some View {
return Rectangle()
.frame(width: UIScreen.screenWidth, height: UIScreen.screenHeight)
.foregroundColor(Color.black.opacity(highlightedBackground ? overlayOpacity : 0))
.position(x: UIScreen.screenWidth / 2, y: (UIScreen.screenHeight) / 2)
.edgesIgnoringSafeArea([.top, .bottom])
.overlay(
Group {
VStack(spacing: 0) {
if !isHandleHidden {
Handle()
}
self.content()
Spacer()
}
}
.frame(width: UIScreen.screenWidth, height: UIScreen.screenHeight)
.background(backgroundColor != nil ? backgroundColor! : Color.black)
.cornerRadius(withCorners ? 40.0 : 0)
.shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
.offset(y: position(from: position) + dragState.translation.height)
.animation(dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0), value: UUID())
.edgesIgnoringSafeArea([.top, .bottom])
.onTapGesture {}
.onChange(of: position) { _ in
withAnimation(.easeInOut) {
highlightedBackground.toggle()
}
}
)
.onTapGesture {
position = position == .bottom ? .top : .bottom
}
}
private func position(from cardPosition: SlideCardPosition) -> CGFloat {
switch cardPosition {
case .top: return UIScreen.screenHeight - contentHeight - UIScreen.topSafeAreaHeight
case .bottom: return 1000
}
}
}
enum SlideCardPosition {
case top
case bottom
}
private enum SlideDragState {
case inactive
case dragging(translation: CGSize)
var translation: CGSize {
switch self {
case .inactive:
return .zero
case .dragging(let translation):
return translation
}
}
var isDragging: Bool {
switch self {
case .inactive:
return false
case .dragging:
return true
}
}
}
private struct Handle: View {
private let handleThickness: CGFloat = 5
var body: some View {
RoundedRectangle(cornerRadius: handleThickness / 2.0)
.frame(width: 34, height: handleThickness)
.foregroundColor(.white)
.padding(.top, 8)
}
}
import UIKit
extension UIScreen {
static let screenWidth = UIScreen.main.bounds.size.width
static let screenHeight = UIScreen.main.bounds.size.height
static let screenSize = UIScreen.main.bounds.size
private static let window = UIApplication.shared.windows[0]
private static let safeFrame = window.safeAreaLayoutGuide.layoutFrame
static var topSafeAreaHeight: CGFloat {
safeFrame.minY
}
static var bottomSafeAreaHeight: CGFloat {
window.frame.maxY - safeFrame.maxY
}
}
import SwiftUI
struct PopUpUIView: View {
#Binding var shouldShow: SlideCardPosition
#State var text1 = "some random text"
var body: some View {
SlideOverCard(position: $shouldShow,
contentHeight: 300) {
VStack(spacing: 10) {
Text(text1)
.foregroundColor(.white)
.padding(.top, 80)
}
}.onChange(of: shouldShow) { _ in
if shouldShow == .top {
text1 = UUID().uuidString
}
}
}
}
struct PopUpUIView_Previews: PreviewProvider {
static var previews: some View {
PopUpUIView(shouldShow: .constant(.bottom))
}
}
Example of incorrect animation with a dynamic text.
Example of what I want to achieve. It is working fine if text is static.
From the code below, I can't figure out why Preference key is not read from the view put in background of HStack. Strangely, If I put that BGViewSetter to the Text Views inside ForEach loop, it works. Unless I have ommitted something too obvious, it looks like a bug. Anybody can confirm it? Thanks
xcode12.2
ios 14.2
struct TestRectPreference : Equatable
{
var frame : CGRect
}
struct TestRectPreferenceKey : PreferenceKey
{
typealias Value = TestRectPreference
static var defaultValue: TestRectPreference = TestRectPreference(frame: CGRect.zero)
static func reduce(value: inout TestRectPreference, nextValue: () -> TestRectPreference)
{
value.frame = nextValue().frame
}
}
struct BGViewSetter: View {
var body: some View {
GeometryReader { geometry in
RoundedRectangle(cornerRadius: 25.0)
.fill(Color.init(#colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1)))
.preference(key: TestRectPreferenceKey.self,
value: TestRectPreference(frame: geometry.frame(in: .global)))
}
}
}
struct FinalView : View
{
#State var offsetX : CGFloat = .zero
#State var info = ""
#State var size : CGFloat = .zero
var body: some View
{
VStack
{
Text("FinalView : \(info)")
HStack
{
ForEach( 1 ..< 10)
{ i in
Text("\(i)")
.frame(width: 100)
.opacity(0.8)
}
}
.background(BGViewSetter())
.animation(.easeOut)
.gesture(
DragGesture()
.onChanged
{ gesture in
self.offsetX = gesture.translation.width
}
.onEnded
{ _ in
self.offsetX = .zero
}
)
.offset(x: self.offsetX)
Text("Footer")
Divider()
Spacer()
}
.onPreferenceChange(TestRectPreferenceKey.self)
{
self.size = $0.frame.height
self.info = "Pref Chng : \(self.size)"
}
}
}
struct PreferenceKeyTest_Previews: PreviewProvider {
static var previews: some View {
FinalView()
}
}
I created a circularprogress view to be able to show a progress bar according to the steps data. But for some reason I can not reach to the step.count inside my stepView file.
This is my StepView
struct StepView: View {
private var healthStore: HealthStore?
#State private var presentClipboardView = true
#State private var steps: [Step] = [Step]()
init() {
healthStore = HealthStore()
}
private func updateUIFromStatistics(_ statisticsCollection: HKStatisticsCollection) {
let now = Date()
let startOfDay = Calendar.current.startOfDay(for: now)
statisticsCollection.enumerateStatistics(from: startOfDay, to: now) { (statistics, stop) in
let count = statistics.sumQuantity()?.doubleValue(for: .count())
let step = Step(count: Int(count ?? 0), date: statistics.startDate, wc: Double(count ?? 0 / 1000 ))
steps.append(step)
}
}
var body: some View {
VStack {
ForEach(steps, id: \.id) { step in
VStack {
HStack{
Text("WC")
Text("\(step.wc)")
}
HStack {
Text("\(step.count ?? 0)")
Text("Total Steps")
}
Text(step.date, style: .date)
.opacity(0.5)
CircularProgress(steps: step.count) //ERROR
Spacer()
}
}
.navigationBarBackButtonHidden(true)
}
.onAppear() {
if let healthStore = healthStore {
healthStore.requestAuthorization { (success) in
if success {
healthStore.calculateSteps { (statisticsCollection) in
if let statisticsCollection = statisticsCollection {
updateUIFromStatistics(statisticsCollection)
}
}
}
}
}
}
.onDisappear() {
self.presentClipboardView.toggle()
}
}
}
and this is my circularprogress view
struct CircularProgress: View {
var steps: Binding<Int>
var body: some View {
ZStack {
Color.progressBarColor
.edgesIgnoringSafeArea(.all)
VStack {
ZStack {
Label()
Outline(steps: steps)
}
}
}
}
}
struct Label: View {
var percentage: CGFloat = 20
var body : some View {
ZStack {
Text(String(format: "%.0f", percentage))
.font(Font.custom("SFCompactDisplay-Bold", size: 56))
}
}
}
struct Outline: View {
var steps: Binding<Int>
var percentage: CGFloat = 20
var colors : [Color] = [Color.trackProgressBarColor]
var body: some View {
ZStack {
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle()
.trim(from: 0, to: percentage * 0.01)
.stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
.fill(AngularGradient(gradient: .init(colors: colors), center: .center, startAngle: .zero, endAngle: .init(degrees: 360)))
).animation(.spring(response: 2.0, dampingFraction: 1.0, blendDuration: 1.0))
}
}
}
I am getting this error at stepview WHILE CALLING CIRCULARPROGRESS inside the stepview. I guess I am trying to get the data in the wrong way.
I don't see necessity of binding here, so just replace corresponding places with simple Int:
struct CircularProgress: View {
var steps: Int
and
struct Outline: View {
var steps: Int
I'm having a hard time getting subviews to lay out correctly, especially the heights. I hate to hard code the subview heights but that is the only thing I can get to work. If I remove these frame(heights) from the subviews, the subviews overlap in the parent view. Any suggestions for a better way to architect this without the hard coded heights?
struct SkateDetailView: View {
#EnvironmentObject var combineWorkoutManager: CombineWorkoutManager
#Environment(\.colorScheme) var colorScheme
#State var subscriptionIsActive = false
var body: some View {
ScrollView {
VStack(alignment: .leading) {
combineWorkoutManager.workout.map { SkateDetailHeaderView(workout: $0)}
combineWorkoutManager.heartRateSamplesFromWorkout.map { HeartRateRecoveryCard(heartRateSamples: $0) }
combineWorkoutManager.vo2MaxFromWorkout.map { vo2MaxCard(vo2MaxValue: $0) }
}
.padding(.top)
.background(colorScheme == .dark ? Color.black : Color.offWhite)
.onAppear {
combineWorkoutManager.loadHeartRatesFromWorkout()
combineWorkoutManager.loadVo2MaxFromWorkout()
combineWorkoutManager.getWorkout()
self.subscriptionIsActive = UserDefaults.standard.bool(forKey: subscriptionIsActiveKey)
}
//Code for conditional view modifer from: https://fivestars.blog/swiftui/conditional-modifiers.html
.if(subscriptionIsActive) { $0.redacted(reason: .placeholder)}
}
}
}
struct SkateDetailHeaderView: View {
var workout: HKWorkout
#Environment(\.colorScheme) var colorScheme
#State var sessionTypeImage = Image("Game_playerhelmet")
var body: some View {
ZStack(alignment: .leading) {
(colorScheme == .dark ? Color.black : Color.white)
.cornerRadius(10)
VStack(alignment: .leading) {
HStack() {
sessionTypeImage
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 35.0, height: 35.0)
.unredacted()
Text(WorkoutManager.ReadOutHockeyTrackerMetadata(workout: workout)?.sessionType ?? "")
.font(.custom(futuraLTPro, size: largeTitleTextSize))
.unredacted()
}
.padding(.leading)
.onAppear {
setSessionTypeImage()
}
HStack {
Text("Goals")
Text("Assists")
Text("+/-")
}
.font(.custom(futuraMedium, size: captionTextSize))
.unredacted()
}
.padding(.all)
}
.padding(.horizontal)
}
struct HeartRateRecoveryCard: View {
//Don't download HR samples for this view the parent view should download HR samples and pass them into it's children views
#State var heartRateSamples: [HKQuantitySample]
#State var HRRAnchor = 0
#State var HRRTwoMinutesLater = 0
#State var heartRateRecoveryValue = 0
#Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
(colorScheme == .dark ? Color.black : Color.white)
.cornerRadius(10)
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("Heart Rate Recovery")
.font(.custom(futuraLTPro, size: titleTextSize))
.unredacted()
Text("\(heartRateRecoveryValue) bpm")
.font(.custom(futuraMedium, size: titleTextSize))
.font(Font.body.monospacedDigit())
Text("\(HRRAnchor) bpm - \(HRRTwoMinutesLater) bpm")
.font(.custom(futuraMedium, size: captionTextSize))
.font(Font.body.monospacedDigit())
.foregroundColor(Color(.secondaryLabel))
}
.padding(.leading)
SwiftUILineChart(chartLabelName: "Heart Rate Recovery", entries: convertHeartRatesToLineChartDataAndSetFormatter(heartRates: heartRateSamples).0, chartLineAndGradientColor: ColorCompatibility.label, xAxisFormatter: convertHeartRatesToLineChartDataAndSetFormatter(heartRates: heartRateSamples).1, showGradient: false)
.frame(width: 350, height: 180, alignment: .center)
.cornerRadius(12)
.onAppear {
if let unwrappedHRRObject = HeartRateRecoveryManager.calculateHeartRateRecoveryForHRRGraph(heartRateSamples: heartRateSamples) {
HRRAnchor = unwrappedHRRObject.anchorHR
HRRTwoMinutesLater = unwrappedHRRObject.twoMinLaterHR
heartRateRecoveryValue = unwrappedHRRObject.hrr
print("HRR = \(heartRateRecoveryValue)")
}
}
BarScaleView(valueToBeScaled: heartRateRecoveryValue, scaleMax: 60, numberOfBlocks: 5, colors: [logoAquaShade1Color, logoAquaShade2Color, logoAquaShade3Color, logoAquaShade4Color, logoAquaShade5Color], blockValues: [0, 12, 24, 36, 48])
}
.padding(.top)
}
.frame(height: 375)
.padding(.all)
}
struct vo2MaxCard: View {
#State var vo2MaxValue: Int
#Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
(colorScheme == .dark ? Color.black : Color.white)
.cornerRadius(10)
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("VO2 Max")
.font(.custom(futuraLTPro, size: titleTextSize))
.padding(.top)
.unredacted()
Text("\(vo2MaxValue) mL/(kg - min)")
.font(.custom(futuraMedium, size: titleTextSize))
.font(Font.body.monospacedDigit())
}
.padding(.leading)
BarScaleView(valueToBeScaled: vo2MaxValue, scaleMax: 72, numberOfBlocks: 4, colors: [logoRedShade1Color, logoRedShade2Color, logoRedShade3Color, logoRedShade4Color, logoRedShade5Color], blockValues: [0, 18, 36, 54])
}
}
.frame(height: 175)
.padding(.all)
}
}
struct BarScaleView: View {
var valueToBeScaled: Int
var scaleMax: Int
var numberOfBlocks: Int
var colors: [UIColor]
var blockValues: [Int] //e.g. 0, 12, 24, 36, 48
var body: some View {
GeometryReader { geometry in
VStack {
HStack(spacing: 0) {
ForEach(0..<numberOfBlocks) { index in
ScaleBarBlock(numberOfBlocks: numberOfBlocks, blockColor: Color(colors[index]), proxy: geometry, blockValue: blockValues[index])
}
}
Image(systemName: "triangle.fill")
.padding(.top)
.unredacted()
// .if(subscriptionIsActive) { $0.redacted(reason: .placeholder)}
.if(valueToBeScaled >= scaleMax) { $0.position(x: geometry.size.width - 5) }
.if(valueToBeScaled < scaleMax) { $0.position(x: geometry.size.width * CGFloat(Double(valueToBeScaled) / Double(scaleMax))) }
}
}
.padding()
}
}
struct SwiftUILineChart: UIViewRepresentable {
var chartLabelName: String
//Line Chart accepts data as array of BarChartDataEntry objects
var entries: [ChartDataEntry]
var chartLineAndGradientColor = logoRedShade1Color
var xAxisFormatter: ChartXAxisFormatter?
var showGradient = true
// this func is required to conform to UIViewRepresentable protocol
func makeUIView(context: Context) -> LineChartView {
//crate new chart
let chart = LineChartView()
//it is convenient to form chart data in a separate func
chart.data = addData()
chart.backgroundColor = .clear
chart.pinchZoomEnabled = false
chart.dragEnabled = false
chart.isUserInteractionEnabled = false
//If available pass xAsisFormatter to chart
if let unwrappedxAxisFormatter = xAxisFormatter {
chart.xAxis.valueFormatter = unwrappedxAxisFormatter
chart.xAxis.setLabelCount(5, force: true) //set number of labels at top of graph to avoid too many (Not using since setting granularity works)
chart.xAxis.avoidFirstLastClippingEnabled = true
chart.xAxis.labelPosition = .bottom
}
return chart
}
// this func is required to conform to UIViewRepresentable protocol
func updateUIView(_ uiView: LineChartView, context: Context) {
//when data changes chartd.data update is required
uiView.data = addData()
}
func addData() -> LineChartData {
let data = LineChartData()
//BarChartDataSet is an object that contains information about your data, styling and more
let dataSet = LineChartDataSet(entries: entries)
if showGradient == true {
let gradientColors = [chartLineAndGradientColor.cgColor, UIColor.clear.cgColor]
let colorLocations: [CGFloat] = [1.0, 0.0] //positioning of gradient
let gradient = CGGradient(colorsSpace: nil, colors: gradientColors as CFArray, locations: colorLocations)!
dataSet.fillAlpha = 1
dataSet.fill = Fill(linearGradient: gradient, angle: 90)
dataSet.drawFilledEnabled = true
}
// change bars color to green
dataSet.colors = [chartLineAndGradientColor]
dataSet.drawCirclesEnabled = false //no circles
dataSet.drawValuesEnabled = false //hide labels on the datapoints themselves
//change data label
dataSet.label = chartLabelName
data.addDataSet(dataSet)
return data
}
}
So I have a circular progress bar that is declared like this
struct ProgressBar: View {
var progress: CGFloat
var body: some View {
let gradient = LinearGradient(...)
ZStack {
Circle()
.stroke(lineWidth: 25)
.opacity(0.3)
.foregroundColor(Color.secondary)
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(gradient ,style: StrokeStyle(lineWidth: 25.0, lineCap: .round, lineJoin: .round))
.rotationEffect(Angle(degrees: 270.0))
.animation(.linear)
}
}
}
The variable progress is passed into the view and is just simple division of value/total where I have two buttons to reduce or increase value and I use the animation to update the progress cleanly.
I call this view in another view called DetailView
However for some reason the second secondView floats in from the top when I navigate to DetailView from another view. What is happening?
Im not sure if this is a common issue but I can share a video if that might help you.
As requested here is an example.
import SwiftUI
struct ContentView: View {
#State var bookData: [Book] = load("list")
#State private var selectedBook: Book? = nil
#State var showOnboarding = false
var body: some View {
NavigationView {
Form {
Section{
ForEach(bookData){ bookDetail in
NavigationLink(
destination: PageDetail(bookData: $bookData, book: bookDetail),
label: {
BookView(book: bookDetail)
})
}
}
}
}
}
import SwiftUI
struct PageDetail: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#Binding var bookData: [Book]
#State var count = 0
var progress: CGFloat{
let page = value
let total = Int(book.total) ?? 1
let answer = CGFloat(page)/CGFloat(total)
return answer
}
var value: Int{
let page = Int(book.page) ?? 1
let cnt = count
let calc = page + cnt
return calc
}
var book: Book{
var body: some View {
ZStack{
LinearGradient(...)
VStack {
VStack(spacing: -25){
ProgressBar(page: value,total: book.total ,progress: progress)
.frame(width: 250, height: 300)
.padding(.horizontal, 20)
HStack {
Button(action: {
self.count -= 1
}, label: {
Image(systemName: "minus.circle")
})
Button(action: {
self.count += 1
}, label: {
Image(systemName: "plus.circle")
})
}
}
}
}
}
struct ProgressBar: View {
var page: Int
var total: String
var progress: CGFloat
var body: some View {
let gradient = LinearGradient(...)
ZStack {
Circle()
.stroke(lineWidth: 25)
.opacity(0.3)
.foregroundColor(Color.secondary)
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(gradient ,style: StrokeStyle(lineWidth: 25.0, lineCap: .round, lineJoin: .round))
.rotationEffect(Angle(degrees: 270.0))
.animation(.linear)
}
}
}
struct PageDetail_Previews: PreviewProvider {
#State static var previewed = testData
static var previews: some View {
PageDetail(bookData: $previewed, book: previewed[0])
}
}
Only idea - try to make animation value dependent, like below
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(gradient ,style: StrokeStyle(lineWidth: 25.0, lineCap: .round, lineJoin: .round))
.rotationEffect(Angle(degrees: 270.0))
.animation(.linear, value: progress) // << here !!