I've made a similiar question yesterday, but I have fixed the problem. Now I have another issue and I made a new question to ask.
I've made a Circular Timer which countdown : CircularTimer
But if I'm closing the view, and open it again, it starts again from scratch. How can I save the progress of the timer or something like that, so if I'm closing the view, when I'm coming back to be where it was last time ?
Thanks !
This is WaitingOrderView.
struct WaitingOrderView: View {
#State private var timer: AnyCancellable?
#EnvironmentObject var syncViewModel : SyncViewModel
var body: some View {
ZStack {
if syncViewModel._order.id == 0 && syncViewModel._order.status == 0 {
CartView()
}
else if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.accepted.rawValue
})?.id
{
OrderConfirmedView()
}
}
.onAppear() {
startFetchStatus()
}
}
func startFetchStatus() {
timer = Timer.publish(every: 20, on: .main, in: .common)
.autoconnect()
.sink { _ in
syncViewModel.fetchOrder(id: syncViewModel._order.id)
}
}
}
The function startFetchStatus() gets the data from the backend every 20 seconds, and it looks like this, for example: Fetch response
This is the CircularTimer View :
let timer = Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect()
#available(iOS 15, *)
struct CircularTimer: View {
var orderDate : Date
var orderDeliveryDate : Date
#State var onTick: CGFloat = 1
let date = Date()
var body: some View {
VStack(spacing : 0){
Image("clock_button")
ZStack{
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().stroke(Color.gray.opacity(22/100), lineWidth: 5)
)
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().trim(from:0, to: onTick)
.stroke(
style: StrokeStyle(
lineWidth: 5,
lineCap: .round ,
lineJoin:.round
)
)
.foregroundColor(
( completed() ? Color.orange: Color.orange)
).animation(
.easeInOut(duration: 0.2)
)
)
.rotationEffect(Angle(degrees: 270.0))
Image("indicator_ellipse")
.resizable()
.frame(width: 230, height: 230)
}
}.onReceive(timer) { time in
progress(time: Int(time.timeIntervalSince1970))
}
}
func completed() -> Bool {
return onTick == 1
}
func progress(time: Int) {
let minutesOrderDeliveryDate = Int(orderDeliveryDate.timeIntervalSince1970)
let minutesOrderDate = Int(orderDate.timeIntervalSince1970)
let minutesCurrentDate = time
let totalMinutes = minutesOrderDeliveryDate - minutesOrderDate
let remainingMinutes = minutesOrderDeliveryDate - minutesCurrentDate
onTick = CGFloat(remainingMinutes) / CGFloat(totalMinutes)
print(onTick)
}
func dateFormatTime(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = .current
return dateFormatter.date(from: date) ?? Date.now
}
}
This is OrderConfirmedView :
struct OrderConfirmedVieww: View {
#EnvironmentObject var syncViewModel : SyncViewModel
#State var nowDate: Date = Date()
var body: some View {
VStack {
Spacer()
Text(Texts.orderConfirmedText1)
.font(.title)
.fontWeight(.semibold)
.foregroundColor(.colorGrayDark)
.multilineTextAlignment(.center)
.lineLimit(3)
.padding(40)
CircularTimer(orderDate: dateFormatTime(date: syncViewModel._order.date ?? ""), orderDeliveryDate: dateFormatTime(date: syncViewModel._order.deliveryDate ?? ""))
.padding()
// Spacer()
Text(Texts.orderOraLivrareText)
.font(.headline)
.fontWeight(.thin)
.padding(.bottom)
Text(dateFormatTime(date: syncViewModel._order.deliveryDate ?? ""), style: .time)
.font(.title2)
.fontWeight(.bold)
Spacer()
Spacer()
Button {
} label: {
Text(Texts.orderButtonText)
.font(.headline)
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.onboardingColor)
.cornerRadius(20)
}
.padding()
}
}
func dateFormatTime(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = .current
return dateFormatter.date(from: date) ?? Date.now
}
}
Related
My app uses CoreData to store one quote for each day. There's a widget, the timeline of which is set to update after midnight and fetch the quote for that particular date. This works fine. However, I noticed that when I click on the widget with the new day's quote, the app - which has remained in the background - still shows the previous day's quote. Removing the app from the list of open ones and reopening it shows the correct information.
Is there a way to force a refresh of the DailyQuoteView and make sure that there isn't leftover data?
EDIT: Here's the daily quote struct that I'd like to always show the relevant data when brought from the background.
import SwiftUI
struct DailyQuoteView: View {
#Environment(\.scenePhase) var scenePhase
#State var id: UUID = .init()
#EnvironmentObject var unsplashModel: UnsplashModel
let managedObjectContext = CoreDataStack.shared.managedObjectContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "date", ascending: true)],
predicate: NSPredicate(
format: "date >= %# AND date <= %#",
argumentArray: [
Calendar.current.startOfDay(for: Date()),
Calendar.current.date(byAdding: .minute, value: 1439, to: Calendar.current.startOfDay(for: Date()))!
]
)
) var quote: FetchedResults<QuoteEntity>
#FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
) var quotes: FetchedResults<QuoteEntity>
#State var shouldPresentSheet = false
#State var animateCard = false
#Binding var cardOffset: Double
#Binding var coverImageSize: Double
#State var showAboutScreen = false
// MARK: - ELEMENTS
var coverImage: some View {
Image(uiImage: UIImage(data: quote.first?.image ?? Data()) ?? UIImage())
.resizable()
.scaledToFill()
}
var aboutButton: some View {
Button {
showAboutScreen.toggle()
} label: {
Image(systemName: "info")
.foregroundColor(.primary)
.padding(10)
.background(Circle().fill(Color("background")))
.font(.title3)
}
.padding()
.shadow(radius: 5)
}
var titleView: some View {
Text(quote.first?.title ?? "")
.font(.largeTitle)
.multilineTextAlignment(.center)
.bold()
.minimumScaleFactor(0.5)
}
var contentsBox: some View {
ScrollView {
VStack (alignment: .leading, spacing: 10) {
Text(quote.first?.summary ?? "")
.font(.body)
.multilineTextAlignment(.leading)
.padding(.bottom, 20)
if let username = quote.first?.imageUserName {
HStack(spacing: 0) {
Text("Photo: ")
Link(
destination: URL(string: "https://unsplash.com/#\(username)?utm_source=quote_of_the_day&utm_medium=referral")!,
label: {
Text((quote.first?.imageUser)!)
.underline()
})
Text(" on ")
Link(
destination: URL(string: "https://unsplash.com/")!,
label: {
Text("Unsplash")
.underline()
})
Spacer()
}
.font(.caption)
.italic()
.foregroundColor(.gray)
.padding(.vertical, 3)
}
}
.font(.subheadline)
}
}
var buttonRow: some View {
HStack {
Menu(
content: {
Button {
shouldPresentSheet.toggle()
} label: {
Text("as image")
Image(systemName: "photo")
}
ShareLink(item: quote.first!.title, message: Text(quote.first!.summary)) {
Text("as text")
Image(systemName: "text.justify.left")
}
},
label: {
HStack {
Image(systemName: "square.and.arrow.up")
Text("Share")
}
.padding(10)
.background(RoundedRectangle(cornerRadius: 10).fill(Color("whitesand")))
.tint(Color(.secondaryLabel))
}
)
Spacer()
Link(destination: URL(string: quote.first!.link)!) {
Image(systemName: "book")
Text("Read more")
}
.padding(10)
.background(RoundedRectangle(cornerRadius: 10).fill(Color("vpurple")))
.tint(Color(.white))
}
.frame(height: 60)
.sheet(isPresented: $shouldPresentSheet) {
QuoteShare(quote: quote.first!)
}
}
var cardView: some View {
Rectangle()
.fill((Color("background")))
.cornerRadius(50, corners: [.topLeft, .topRight])
.overlay{
VStack{
titleView
.padding(.top, 30)
contentsBox
Spacer()
buttonRow
}
.padding(.horizontal, 20)
.frame(maxWidth: 600)
}
}
// MARK: - BODY
var body: some View {
ZStack{
GeometryReader{ proxy in
coverImage
.ignoresSafeArea()
.frame(width: UIScreen.main.bounds.width, height: proxy.size.height * (animateCard ? 0.30 : 1.0))
.animation(.easeInOut(duration: 1), value: animateCard)
}
GeometryReader { proxy in
aboutButton
.frame(width: 30, height: 30)
.position(x: proxy.frame(in: .global).maxX - 30, y: proxy.frame(in: .global).minY)
}
GeometryReader{ proxy in
VStack(spacing: 0){
Spacer()
cardView
.frame(height: proxy.size.height * 0.8)
.offset(y: animateCard ? 0 : cardOffset)
.animation(.spring().delay(0.2), value: animateCard)
}
}
}
.id(id)
.blur(radius: shouldPresentSheet ? 5.0 : 0.0)
.brightness(shouldPresentSheet ? -0.2 : 0.0)
.animation(.easeIn, value: shouldPresentSheet)
.onAppear {
if !imagesLoaded {
unsplashModel.loadData(imageCount: quotes.count)
imagesLoaded = true
}
animateCard = true
}
.onChange(of: unsplashModel.dailyImageData) { _ in
if unsplashModel.dailyImageData.count == quotes.count {
updateDailyImages()
}
}
.onChange(of: scenePhase, perform: { newValue in
if newValue == .active {
id = .init()
}
})
.task {
NotificationManager.shared.scheduleNotifications(notificationText: quote.first?.title ?? "")
}
}
}
EDIT 2: Code updated with lorem ipsum's suggestion below (.id and onChange of scenePhase). However, this seems to interfere with the card animation in .onAppear { withAnimation { animateCard = true } }
I'm new to Swift at the moment and I'm having trouble appending to a to-do list. I am not receiving an error, so I don't know what's wrong. I hope this isn't too much code to go through, but I don't even know where the problem is.
There is a sheet modal that opens and closes fine. The only issue is that when I press the save button, the information I typed in doesn't add to the list.
I've add the ViewModel, NewTaskView(sheet), TaskView(customizes list), and a bit of code of the DetailView where the list should be.
import Foundation
import SwiftUI
class TaskViewModel : Identifiable , ObservableObject {
#Published var tasks : [Task] = [
Task(taskName: "Lab", taskDate: Date(), taskCompleted: false),
Task(taskName: "Assignment 4.02", taskDate: Date(), taskCompleted: false)
]
#Published var sortType : SortType = .alphabetical
#Published var isPresented = false
#Published var searched = ""
func addTask(task : Task){
tasks.append(task)
}
func removeTask(indexAt : IndexSet){
tasks.remove(atOffsets: indexAt)
}
func sort(){
switch sortType {
case .alphabetical :
tasks.sort(by: { $0.taskName < $1.taskName })
case .date :
tasks.sort(by: { $0.taskDate > $1.taskDate })
}
}
}
struct Task : Identifiable , Equatable {
var id : String = UUID().uuidString
let taskName : String
let taskDate : Date
var taskCompleted: Bool
}
enum SortType : String , Identifiable , CaseIterable {
var id : String { rawValue }
case alphabetical
case date
}
struct NewTaskView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var taskVM : TaskViewModel
#State var taskName = ""
#State var taskCompleted = Bool()
#State var taskDate = Date()
var body: some View {
NavigationView {
VStack(spacing: 14) {
Spacer()
TextField("Assignment Name",text: $taskName)
.padding()
.background(Color("tan"))
.cornerRadius(5)
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
.padding(.bottom, 20)
HStack{
Text("Due Date")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
Image(systemName: "bell.badge")
.foregroundColor(.gray)
.frame(width: 30, height: 30, alignment: .leading)
VStack{
DatePicker("Select Date", selection: $taskDate, displayedComponents: .date)
.labelsHidden()
}
}
HStack{
Text("Mark as Completed")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
Button(action : {
taskCompleted.toggle()
}){
if taskCompleted == true {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color("orange"))
.font(.title2)
}
else {
Image(systemName: "circle")
.foregroundColor(Color("lightorange"))
.font(.title2)
}
}
}
Spacer()
Button(action:{taskVM.addTask(task: .init(taskName: taskName, taskDate: taskDate, taskCompleted: taskCompleted))
presentationMode.wrappedValue.dismiss()},
label:{
Text("Save")
})
}.padding()
.navigationBarItems(trailing: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
HStack{
Image(systemName: "xmark")
.foregroundColor(Color("orange"))
}
})
}
}
}
struct NewTaskView_Previews: PreviewProvider {
static var previews: some View {
NewTaskView(taskVM: TaskViewModel())
}
}
struct TaskView: View {
var task : Task
#Environment(\.managedObjectContext) var moc
#State var currentDate: Date = Date()
#State var taskCompleted = false
func getDateFormatString(date:Date) -> String
{
var dateString = ""
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM. dd"
dateString = dateFormatter.string(from: date)
return dateString
}
var body: some View {
HStack {
Button(action : {
self.taskCompleted.toggle()
try? self.moc.save()
}){
if self.taskCompleted {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color("orange"))
.font(.title2)
}
else {
Image(systemName: "circle")
.foregroundColor(Color("lightorange"))
.font(.title2)
}
}.padding()
VStack (alignment: .leading, spacing: 2){
Text("\(task.taskName)")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 18))
.foregroundColor(Color("dark"))
.padding([.leading, .trailing], 1)
if self.taskCompleted {
Text("Completed")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 12))
.foregroundColor(Color("burntorange"))
.frame(width: 95, height: 20)
.background(Color("lightorange"))
.cornerRadius(4)
}
else if currentDate > task.taskDate {
Text("Late")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 12))
.foregroundColor(.white)
.frame(width: 95, height: 18)
.background(Color("lightbrown"))
.cornerRadius(4)
}
else {
let diffs = Calendar.current.dateComponents([.day], from: currentDate, to: task.taskDate )
Text("\(diffs.day!) days left")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 12))
.foregroundColor(Color("burntorange"))
.frame(width: 95, height: 20)
.background(Color("lightorange"))
.cornerRadius(4)
}
}
Spacer()
Text(getDateFormatString(date: task.taskDate ))
.font(Font.custom(FontNameManager.Montserrat.medium, size: 12))
.padding()
}
.padding(10)
.cornerRadius(10)
.background(
RoundedRectangle(cornerRadius: 10 , style: .continuous)
.foregroundColor(.white))
}
}
struct TaskView_Previews: PreviewProvider {
static var previews: some View {
TaskView(task: Task(id: "", taskName: "Task Name", taskDate: Date(), taskCompleted: false))
}
}
struct DetailsView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var developers : Developer
#ObservedObject var taskVM : TaskViewModel
#Environment(\.presentationMode) var presen
#Environment(\.editMode) var editButton
#State var taskCompleted = false
#State var show = false
#State var showSearch = false
#State var txt = ""
#State var currentDate: Date = Date()
#State var image : Data = .init(count: 0)
#State var indices : [Int] = []
func getDateFormatString(date:Date) -> String
{
var dateString = ""
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM. dd"
dateString = dateFormatter.string(from: date)
return dateString
}
var body: some View {
NavigationView {
ZStack {
ZStack{
VStack{
Color("lightorange")
.clipShape(CustomCorners(corners: [.bottomLeft, .bottomRight], size: 70))
.ignoresSafeArea(.all, edges: .top)
.frame(width: 420, height: 175)
Spacer()
}
VStack{
HStack {
if !self.showSearch{
Button(action: {
withAnimation(.spring()){
self.presen.wrappedValue.dismiss()
}
}) {
Image(systemName: "chevron.left")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(10)
}
}
Spacer(minLength: 0)
HStack {
if self.showSearch{
Image(systemName: "magnifyingglass")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(.horizontal, 8)
TextField("Search", text: $taskVM.searched , onEditingChanged: { (isBegin) in
if isBegin {
showSearch = true
} else {
showSearch = false
}
})
Button(action: {
withAnimation{
self.showSearch.toggle()
}
}) {
Image(systemName: "xmark").foregroundColor(.black).padding(.horizontal, 8)
}
}
else {
Button(action: {
withAnimation {
self.showSearch.toggle()
}
}) {
Image(systemName: "magnifyingglass")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(10)
}
}
}.padding(self.showSearch ? 10 : 0)
.background(Color("lightorange"))
.cornerRadius(20)
}
ZStack{
RoundedRectangle(cornerRadius: 25)
.foregroundColor(Color("tan"))
.frame(width: 335, height: 130)
HStack {
VStack (alignment: .leading){
// Image(uiImage: UIImage(data: developers.imageD ?? self.image)!)
// .resizable()
// .frame(width: 70, height: 70)
// .clipShape(RoundedRectangle(cornerRadius: 20))
Text("\(developers.username ?? "")")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 24))
.foregroundColor(Color("dark"))
Text("\(developers.descriptions ?? "")")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 18))
.foregroundColor(.gray)
}
.padding([.leading, .trailing], 70)
.padding(.bottom, 80)
Spacer()
}
}
HStack{
Text("Assignments")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 20))
Spacer(minLength: 0)
EditButton()
}
.padding([.leading, .trailing], 20)
Spacer()
List {
ForEach (taskVM.tasks.filter {
self.taskVM.searched.isEmpty ? true : $0.taskName.localizedCapitalized.contains(self.taskVM.searched)} ){ task in
TaskView(task: task)
.listRowSeparator(.hidden)
}
.onDelete(perform: {
taskVM.removeTask(indexAt: $0)
})
}
.listStyle(InsetListStyle())
}
}
VStack {
Spacer()
HStack {
Spacer()
ExpandableFAB(taskVM: TaskViewModel(), show: $show)
}
}
}.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
}
struct DetailsView_Previews: PreviewProvider {
static let persistenceController = PersistenceController.shared
static var developers: Developer = {
let context = persistenceController.container.viewContext
let developers = Developer(context: context)
developers.username = "Math"
developers.descriptions = "Calculus"
return developers
}()
static var previews: some View {
DetailsView(developers: developers, taskVM: TaskViewModel()).environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
struct ExpandableFAB: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var taskVM : TaskViewModel
#Binding var show: Bool
var body: some View{
VStack(spacing: 20){
if self.show{
Button(action: {
taskVM.isPresented.toggle()
}) {
Image(systemName: "list.bullet.rectangle.portrait").resizable().frame(width: 25, height: 25).padding()
}
.foregroundColor(.white)
.background(Color("orange"))
.clipShape(Circle())
Button(action: {
self.show.toggle()
}) {
Image(systemName: "doc.badge.plus").resizable().frame(width: 25, height: 25).padding()
}.foregroundColor(.white)
.background(Color("orange"))
.clipShape(Circle())
}
Button(action: {
self.show.toggle()
}) {
Image(systemName: "plus").resizable().frame(width: 25, height: 25).padding()
}.foregroundColor(.white)
.background(Color("orange"))
.clipShape(RoundedRectangle(cornerRadius: 20))
.padding()
.rotationEffect(.init(degrees: self.show ? 180 : 0))
}.fullScreenCover(isPresented: $taskVM.isPresented, content: {
NewTaskView(taskVM: taskVM)
})
}
}
#burnsi I don't think I have that anywhere. Where should there be one?
Of course you have one and you need one:
ExpandableFAB(taskVM: TaskViewModel(), show: $show)
This line is causing the issue.
You are somehow injecting a TaskViewModel in your DetailsView (You are not showing where), but not using it. Try:
ExpandableFAB(taskVM: taskVM, show: $show)
And the place you create your ViewModel ('TaskViewModel()') should have a #StateObject wrapper.
Explanation:
As you add something to your ViewModel the views depending on the ViewModel get rebuild. So your TaskViewModel ends up beeing recreated and you still have your 2 initial values in your array.
I have an issue and I hope that I will find the answer here to my questions. I made a Circular Timer, but everytime I'm closing the view , is going back from the start.
I tried to save the value where the time is calculated "onTick", is working for 2-3 seconds, and after again is going back from the start and is countdown from there.
I'll put a picture/video bellow with the behaviour, maybe somone can help me fix it .
Thanks !
This is WaitingOrderView.
struct WaitingOrderView: View {
#State private var timer: AnyCancellable?
#EnvironmentObject var syncViewModel : SyncViewModel
var body: some View {
ZStack {
if syncViewModel._order.id == 0 && syncViewModel._order.status == 0 {
CartView()
}
else if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.accepted.rawValue
})?.id
{
OrderConfirmedView()
}
}
.onAppear() {
startFetchStatus()
}
}
func startFetchStatus() {
timer = Timer.publish(every: 20, on: .main, in: .common)
.autoconnect()
.sink { _ in
syncViewModel.fetchOrder(id: syncViewModel._order.id)
}
}
}
The function startFetchStatus() gets the data from the backend every 20 seconds, and it looks like this, for example: Fetch response
This is the CircularTimer View :
let timer = Timer
.publish(every: 5, on: .main, in: .common)
.autoconnect()
#available(iOS 15, *)
struct CircularTimer: View {
#EnvironmentObject var syncViewModel : SyncViewModel
#State var orderDate : Date
#State var orderDeliveryDate : Date
#State var onTick: Double = 1
#State var savedOnTick : Double = 1
let date = Date()
var body: some View {
VStack(spacing : 0){
Image("clock_button")
ZStack{
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().stroke(Color.gray.opacity(22/100), lineWidth: 5)
)
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().trim(from:0, to: onTick)
.stroke(
style: StrokeStyle(
lineWidth: 5,
lineCap: .round ,
lineJoin:.round
)
)
.foregroundColor(
( completed() ? Color.orange : Color.orange)
).animation(
.easeInOut(duration: 0.2)
)
)
.rotationEffect(Angle(degrees: 270.0))
Image("indicator_ellipse")
.resizable()
.frame(width: 230, height: 230)
}
.onAppear {
getData()
print(savedOnTick)
}
.onDisappear {
saveData()
}
}
.onReceive(timer) { time in
progress(time: Int(time.timeIntervalSince1970))
}
}
func completed() -> Bool {
return onTick == 1
}
func progress(time: Int) {
let minutesOrderDeliveryDate = Int(orderDeliveryDate.timeIntervalSince1970)
let minutesOrderDate = Int(orderDate.timeIntervalSince1970)
let minutesCurrentDate = time
let totalMinutes = minutesOrderDeliveryDate - minutesOrderDate
let remainingMinutes = minutesOrderDeliveryDate - minutesCurrentDate
onTick = CGFloat(remainingMinutes) / CGFloat(totalMinutes)
}
func dateFormatTime(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.calendar = Calendar(identifier: .gregorian)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
return dateFormatter.date(from: date) ?? Date()
}
func saveData() {
UserDefaults.standard.set(onTick, forKey: "onTick")
}
func getData() {
onTick = UserDefaults.standard.double(forKey: "onTick")
}
}
This is OrderConfirmedView :
struct OrderConfirmedVieww: View {
#EnvironmentObject var syncViewModel : SyncViewModel
#State var nowDate: Date = Date()
var body: some View {
VStack {
Spacer()
Text(Texts.orderConfirmedText1)
.font(.title)
.fontWeight(.semibold)
.foregroundColor(.colorGrayDark)
.multilineTextAlignment(.center)
.lineLimit(3)
.padding(40)
CircularTimer(orderDate: dateFormatTime(date: syncViewModel._order.date ?? ""), orderDeliveryDate: dateFormatTime(date: syncViewModel._order.deliveryDate ?? ""))
.padding()
// Spacer()
Text(Texts.orderOraLivrareText)
.font(.headline)
.fontWeight(.thin)
.padding(.bottom)
Text(dateFormatTime(date: syncViewModel._order.deliveryDate ?? ""), style: .time)
.font(.title2)
.fontWeight(.bold)
Spacer()
Spacer()
Button {
} label: {
Text(Texts.orderButtonText)
.font(.headline)
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.onboardingColor)
.cornerRadius(20)
}
.padding()
}
}
func dateFormatTime(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = .current
return dateFormatter.date(from: date) ?? Date.now
}
}
I have a problem and I dont know what's the issue, maybe you can help me out with this. I made a Circular Timer (a circle which count how much time is remaining), but I have 2 big issues .
After I close the view and come back, the timer is starting again from the start and it goes faster. And that's on repeat, after the view is again closed, it starts from the start and it goes faster.
Second issue is that I don't think is counting Right. For example "deliveryDate" is set to be at 4:00 PM, but is 3:58 PM and the Circular Timer is already completed.
NOTE : Is a delivery app, user can select the "deliveryDate".
My timer count with the help of 2 variables:
"syncViewModel._order.date" - This is the Date when the order is placed
and
"syncViewModel._order.deliveryDate". - This is the date that the user want the order to be delieverd
I'll share my code below
This is the Circular Timer :
let timer = Timer
.publish(every: 10, on: .main, in: .common)
.autoconnect()
#available(iOS 15, *)
struct CircularTimerr: View {
#EnvironmentObject var syncViewModel : SyncViewModel
#State var startPointValue : CGFloat
var endPointValue : CGFloat
var body: some View {
VStack{
ZStack{
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().stroke(Color.gray.opacity(22/100), lineWidth: 5)
)
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().trim(from:0, to: progress())
.stroke(
style: StrokeStyle(
lineWidth: 5,
lineCap: .round,
lineJoin:.round
)
)
.foregroundColor(
(completed() ? Color.orange : Color.orange)
).animation(
.easeInOut(duration: 0.2)
)
) }
.onAppear{
print(startPointValue)
print(endPointValue)
}
}.onReceive(timer) { time in
if (self.startPointValue < self.endPointValue) {
self.startPointValue += 1
}
}
}
func completed() -> Bool {
return progress() == 1
}
func progress() -> CGFloat {
return ( startPointValue / endPointValue)
}
}
This is the view
struct OrderConfirmedVieww: View {
#EnvironmentObject var syncViewModel : SyncViewModel
var body: some View {
VStack {
CircularTimer(startPointValue: CGFloat(dateFormatTime(date: syncViewModel._order.date ?? "").timeIntervalSinceNow), endPointValue: CGFloat(dateFormatTime(date: syncViewModel._order.deliveryDate ?? "").timeIntervalSinceNow))
}
}
func dateFormatTime(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = .current
return dateFormatter.date(from: date) ?? Date.now
}
}
Circular Timer Picture
I have an array of views that I need to get values from in order to save them to my context. When trying to loop over these when a button is clicked I get a "Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols" exception. Im very new to swift and I am wondering if there is an easier way to do this. Thank you in advanced!
This is the complete view:
struct DetailedView: View {
#Environment(\.managedObjectContext) var moc
var targetMuscle : String = "Chest"
let today = Date()
#State public var exerciseCards : [ExerciseCard] = []
#State public var exercise : String = "Bench Press"
#State public var exercises : Int = 0
#State public var showPassedWorkouts : Bool = false
static let taskDateFormat : DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
var body: some View {
ZStack{
VStack{
HStack{
VStack{
Text(targetMuscle).font(.system(size:40)).fontWeight(.medium)
Text("\(today, formatter: Self.taskDateFormat)")
.font(.system(size:20))
}.frame(width: 250, height: 30, alignment: .topLeading)
.navigationBarTitle("")
.navigationBarHidden(true)
.padding(.bottom, -7)
Button(action: {
self.showPassedWorkouts.toggle()
}) {
Text("Passed Workouts")
.multilineTextAlignment(.center)
}.offset(x: -75, y: 25)
.sheet(isPresented: $showPassedWorkouts){
PassedWorkoutList()
}
Button(action: {
let workout = Workout(context: self.moc)
workout.muscle = self.targetMuscle
workout.date = formattedDateString(day: self.today)
ForEach(0..<exerciseCards.count, id: \.self){number in
let exercise = Exercise(context: self.moc)
ForEach(0..<self.exerciseCards[number].tableRows.count, id: \.self){innerNum in
let exerciseSet = ExerciseSet(context: self.moc)
exerciseSet.reps = self.exerciseCards[number].tableRows[innerNum].reps
exerciseSet.weight = self.exerciseCards[number].tableRows[innerNum].weight
exerciseSet.set = self.exerciseCards[number].tableRows[innerNum].set
exercise.addToExerciseSet(exerciseSet)
}
workout.addToExercise(exercise)
}
try? self.moc.save()
}) {
Text("Finish")
}.offset(x: -20, y: 20)
}.padding(.bottom, 35)
.padding(.leading)
ScrollView{
ForEach(0..<exerciseCards.count, id: \.self){ number in
self.exerciseCards[number]
}
Button(action: {
self.exerciseCards.append(ExerciseCard())
}) {
Text("Add Exercise")
.frame(minWidth: 325)
.padding()
.foregroundColor(.white)
.background(Color.blue.opacity(0.7))
.cornerRadius(20)
}.padding(.top)
.frame(width: 400)
}
}
}.background(Color.white)
}
}
func formattedDateString(format: String? = "MMM d, h:mm", day: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.string(from: day)
}
The above view also references two views the first one being the ExerciseCard view
struct ExerciseCard: View {
#State public var exercise : String = "Bench Press"
#State public var tableRows : [ExerciseTableRow] = []
var body: some View {
VStack{
TextField("Enter Exercise", text: $exercise).textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 300)
.multilineTextAlignment(.center)
HStack{
Group{
Text("Set")
Text("Weight")
Text("Reps")
}.padding(.horizontal, 30)
.offset(x: -20, y: 0)
}
VStack{
ForEach(0..<tableRows.count, id: \.self){ number in
self.tableRows[number]
}
}.padding(.bottom, 5)
HStack{
Button(action: {
if self.tableRows.count > 1{
self.tableRows.remove(at: self.tableRows.count-1)
}
}) {
Text("Remove Set")
.frame(minWidth: 150)
.padding(.vertical, 5)
.foregroundColor(.white)
.background(Color.red)
.cornerRadius(20)
}
Button(action: {
self.tableRows.append(ExerciseTableRow(set: 2, readOnly: false, setWeight: 2, setReps: 2))
}) {
Text("Add Set")
.frame(minWidth: 150)
.padding(.vertical, 5)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(20)
}
}
}
.padding()
.padding(.vertical)
.background(Color.offWhite)
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.2), radius: 10, x:10, y:10)
.shadow(color: Color.white.opacity(0.7), radius: 10, x:-5, y:-5)
}
}
The second one being the ExerciseTableRow view
struct ExerciseTableRow: View {
#State public var weight : String = "0"
#State public var reps : String = "0"
var set : Int16
var readOnly : Bool
var setWeight : Int16
var setReps : Int16
var body: some View {
HStack{
Text(String(set))
.padding(.trailing, 40)
.padding(.leading, 10)
if readOnly == false{
Group{
TextField("0", text: $weight)
TextField("0", text: $reps)
}.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 50)
.multilineTextAlignment(.center)
.keyboardType(.numberPad)
.padding(.horizontal, 30)
}
else{
Group{
Text(String(setWeight))
Text(String(setReps))
}
.frame(width: 50)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.black, lineWidth: 1)
)
.padding(.bottom, 5)
.padding(.horizontal, 30)
}
}
}
}
The ForEach is a SwiftUI dynamic View container, to build views in cycle. For action you have to use regular swift for .. in expression, like
Button(action: {
let workout = Workout(context: self.moc)
workout.muscle = self.targetMuscle
workout.date = formattedDateString(day: self.today)
for number in 0..<exerciseCards.count {
let exercise = Exercise(context: self.moc)
for innerNum in 0..<self.exerciseCards[number].tableRows.count {
let exerciseSet = ExerciseSet(context: self.moc)
exerciseSet.reps = self.exerciseCards[number].tableRows[innerNum].reps
exerciseSet.weight = self.exerciseCards[number].tableRows[innerNum].weight
exerciseSet.set = self.exerciseCards[number].tableRows[innerNum].set
exercise.addToExerciseSet(exerciseSet)
}
workout.addToExercise(exercise)
}
try? self.moc.save()
}) {
Text("Finish")
}.offset(x: -20, y: 20)