Pause the timer with a button - ios

I am making a timer but am unsure of how to pause it when the button is triggered.
I have attempted to make a boolean #State but I am yet unsure how to pause the timer when the button is triggered. Please review my code below...
struct TestView: View {
#State var isTimeStarted = false
#State var to: CGFloat = 0
#State var timeDuration = 60
#State var time = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State var isPaused = false
#State private var count = 0
var body: some View {
ZStack {
VStack {
ZStack {
Circle()
.trim(from: 0, to: self.to)
.stroke(LinearGradient(gradient: Gradient(colors: [Color.white, Color.white.opacity(0.2)]), startPoint: .topLeading, endPoint: .bottomTrailing), style: StrokeStyle(lineWidth: 15.6, lineCap: .round))
.shadow(radius: 8)
.rotationEffect(.degrees(90))
.rotation3DEffect(Angle(degrees: 180), axis: (x: 1, y: 0, z: 110))
.frame(width: 70, height: 70)
.animation(.easeOut)
.padding()
.padding(.leading, 10)
Text("\(self.timeDuration, specifier: formatTime())")
.font(.system(size: 17.5, design: .rounded))
.fontWeight(.semibold)
.foregroundColor(.white)
.padding(.leading, 10)
}
Button {
isPaused = true
} label: {
ZStack {
BlurView2(style: .systemThinMaterialDark)
.frame(width: 145, height: 45)
.background(Color.yellow)
.cornerRadius(30)
.padding(.horizontal)
Image(systemName: "pause")
.font(.title2)
.shadow(radius: 10)
.foregroundColor(.yellow)
}
}
}
}
.preferredColorScheme(.dark)
.onAppear {
self.timeDuration = 60
withAnimation(.default) {
self.to = 60
}
self.isTimeStarted = true
}
.onReceive(self.time, perform: { _ in
if self.timeDuration != 0 {
self.timeDuration -= 1
withAnimation(.default) {
self.to = CGFloat(self.timeDuration)/60
}
} else {
self.timeDuration = 60
self.to = 60
}
})
}
func formatTime() -> String {
let minutes = Int(timeDuration) / 60 % 60
let seconds = Int(timeDuration) % 60
return String(format: "%02i:%02i", minutes,seconds)
}
}
struct BlurView2: UIViewRepresentable {
var style: UIBlurEffect.Style
func makeUIView(context: Context) -> UIVisualEffectView {
let view = UIVisualEffectView(effect: UIBlurEffect(style: style))
return view
}
func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
}
}

Thi should be pretty simple. Keep your timer firing but return in your function if it should pause.
Add this to the start of your .onReceive function:
if isPaused{
return
}
and change your Button to:
isPaused.toggle()

Related

My system has run out of application memory(app)

This problem appeared when i wrote Text("Today i made:"............), 68 line and i don't know how to fix it.
My thought, that probably problem with #AppStorage(in HomeView i have same variable.... var savedSalary = Double()). But in this View i decided to write #AppStorage with different variable, i just added one more "y" in the end and wrote(var savedSalaryy: Double = 8.75).
Restart didn't help me too.
Before the reading the code watch this screen
import SwiftUI
struct DayOnWorkView: View {
#State private var ifTapped = false
#State private var startTime = Date()
#State private var timerInt = 0 // was string
#AppStorage("SALARY_KEY") var savedSalaryy: Double = 8.75 // was savedSalary = Double()
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
NavigationView {
ZStack {
BackgroundView()
VStack(alignment: .center) {
Button(action: {
self.ifTapped.toggle()
}, label: {
Image(systemName: ifTapped ? "person.crop.circle.badge.checkmark" : "person.crop.circle.badge.xmark")
.resizable()
.scaledToFit()
.frame(width: 40, height: 40)
.onTapGesture {
if !ifTapped {
timerInt = 0
startTime = Date()
}
ifTapped.toggle()
}
Text(ifTapped ? "Online" : "Offline")
.font(.system(size: 30, design: .rounded))
.onTapGesture {
if !ifTapped {
timerInt = 0
startTime = Date()
}
ifTapped.toggle()
}
})
.tint(ifTapped ? Color.green : Color.red)
.shadow(radius: 30)
.opacity(0.9)
.buttonStyle(.borderedProminent)
.buttonBorderShape(.capsule)
.offset(y: 150)
}
VStack {
// timer
Text(String(timerInt))
.font(Font.system(.largeTitle, design: .monospaced))
.foregroundColor(Color.white)
.onReceive(timer) { _ in
if self.ifTapped {
timerInt = Int(Date().timeIntervalSince( self.startTime))
}
}
Text("Today i made: \(Double(Double(savedSalaryy) * Double(Int(timerInt))) / 3600, specifier: "%.2f")")
.offset(y: 150)
.foregroundColor(Color.white)
.bold()
}
}
}
}
}
// Text("Today i made \(Double(Double(savedSalaryy) * Double(Int(timerInt))) / 3600)") WORK WORK WORK
struct DayOnWorkView_Previews: PreviewProvider {
static var previews: some View {
DayOnWorkView()
}
}

I can't modify circular progress bar trim in SwiftUI

I'm making a math game and I'm trying to implement a circular progress bar, where I get errors. I'm not sure if I'm doing anything wrong, but I can't adapt it to the progress of the game, and that's what I want to do. Please look over my code down below...
struct QuestionView: View {
#State var show = false
#State var showSheet: Bool = false
#State var showSheetA: Bool = false
#State var showSheet2: Bool = false
#State private var correctAnswer = 0
#State private var choiceArray: [Int] = [0, 1, 2, 3]
#State private var firstNumber = 0
#State private var secondNumber = 0
#State private var difficulty = 100
#State private var score = 0
#State private var Background1 = "Background 1"
#State private var Background2 = "Background 2"
#State private var Background3 = "Background 3"
#State private var Background4 = "Background 4"
#State private var Background5 = "Background 5"
#State private var Background6 = "Background 6"
#State private var Background7 = "Background 7"
#State private var Background8 = "Background 8"
#State private var Background9 = "Background 9"
#State private var Background10 = "Background 10"
#State private var Background11 = "Background 11"
#State private var Background12 = "Background 12"
#State private var background = [
"Background 1",
"Background 2",
"Background 3",
"Background 4",
"Background 5",
"Background 6",
"Background 7",
"Background 8",
"Background 9",
"Background 10",
"Background 11",
"Background 12",
]
#State var value = "0"
let buttons: [[CalcButton]] = [
[.clear, .negative, .percent, .divide],
[.seven, .eight, .nine, .multiply],
[.four, .five, .six, .subtract],
[.one, .two, .three, .add],
[.zero, .decimal, .equal]
]
#State var currentOperation: Operation = .none
#State var runningNumber = 0
var body: some View {
ZStack {
Image("\(Background1)")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.blur(radius: 20)
.onAppear {
shuffle()
}
VStack {
VStack(alignment: .leading) {
HStack {
HStack {
Circle()
//Where the problem is happening at the trim
.trim(from: CGFloat(show ? Int(0.99) : answerIsCorrect(answer: correctAnswer)), to: 0.01)
.stroke(LinearGradient(gradient: Gradient(colors: [Color.white, Color.white.opacity(0.2)]), startPoint: .topLeading, endPoint: .bottomTrailing), style: StrokeStyle(lineWidth: 17.5, lineCap: .round))
.shadow(radius: 8)
.rotationEffect(.degrees(90))
.rotation3DEffect(Angle(degrees: 180), axis: (x: 1, y: 0, z: 0))
.frame(width: 60, height: 60)
.animation(.easeOut)
.padding()
.padding([.top, .leading])
Spacer()
}
Button {
showSheet.toggle()
} label: {
VStack {
ZStack {
Rectangle()
.fill(.thinMaterial)
.frame(width: 80, height: 40)
.cornerRadius(30)
.padding(.top, 13)
.padding(.trailing, 0)
HStack {
Image(systemName: "circle.grid.3x3.fill")
.foregroundColor(.white)
.font(.title3)
.padding(.top, 13)
.padding(.trailing, 0)
}
}
Text("CALCULATOR")
.font(.system(size: 11))
.padding(.trailing, 0)
}
}
.halfSheet(showSheet: $showSheet) {
ZStack {
Color.black.opacity(0.925).ignoresSafeArea()
VStack {
Spacer()
// Text display
HStack {
Spacer()
Text(value)
.bold()
.font(.system(size: 70))
.foregroundColor(.white)
.minimumScaleFactor(0.5)
}
.padding(.leading)
.padding([.top, .trailing], 23)
.padding(.bottom, 2)
// Our Buttons
ForEach(buttons, id: \.self) { row in
HStack(spacing: 12) {
ForEach(row, id: \.self) { item in
Button {
self.didTap(button: item)
} label: {
Text(item.rawValue)
.font(.system(size: 36))
.frame(width: self.buttonWidth(item: item), height: 55)
.scaledToFit()
.background(item.buttonColor)
.foregroundColor(.white)
.cornerRadius(95)
}
}
}
.padding(.bottom, 0.55)
.padding([.leading, .trailing], 20)
}
}
}
}
Button {
showSheet2.toggle()
} label: {
VStack {
ZStack {
Rectangle()
.fill(.thinMaterial)
.frame(width: 80, height: 40)
.cornerRadius(30)
.padding(.top, 13)
.padding(.trailing, 28.5)
HStack {
Image(systemName: "book")
.foregroundColor(.white)
.font(.title3)
.padding(.top, 13)
.padding(.trailing, 28.5)
}
}
Text("DICTIONARY")
.font(.system(size: 11))
.padding(.trailing, 25.5)
}
}
.halfSheet(showSheet: $showSheet2) {
ZStack {
Home()
}
}
}
.padding(.top, 50)
}
VStack {
Text("Solve the following:")
.foregroundColor(.white)
.font(.title2)
.fontWeight(.medium)
.padding(.bottom, 24)
Text("\(firstNumber) + \(secondNumber)")
.foregroundColor(.white)
.font(.system(size: 60, design: .rounded))
.fontWeight(.medium)
.padding(.bottom, 28)
.onAppear {
generateAnswers()
}
VStack(alignment: .center, spacing: 0.01) {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150), spacing: 2.5)]) {
ForEach(0..<4, id: \.self) { index in
Button {
answerIsCorrect(answer: choiceArray[index])
generateAnswers()
} label: {
AnswerButton(number: choiceArray[index])
}
}
}
Spacer()
}
HStack {
Button {
} label: {
ZStack {
Rectangle()
.fill(Color.white.opacity(0.5))
.frame(maxWidth: 150, maxHeight: 50)
.cornerRadius(15)
.padding([.leading, .trailing])
Image(systemName: "chevron.left")
.font(.title)
}
}
Button {
showSheetA.toggle()
} label: {
VStack {
ZStack {
Rectangle()
.fill(Color.white.opacity(0.5))
.frame(maxWidth: 150, maxHeight: 50)
.cornerRadius(15)
.padding([.leading, .trailing])
Image(systemName: "xmark")
.font(.title)
.foregroundColor(.red)
}
}
}
ZStack {
Rectangle()
.fill(Color.white.opacity(0.5))
.frame(maxWidth: 150, maxHeight: 50)
.cornerRadius(15)
.padding([.leading, .trailing])
Image(systemName: "chevron.right")
.font(.title)
}
}
.padding(.trailing, 6)
.padding(.bottom)
Spacer(minLength: 65)
}
}
}
}
func shuffle() {
Background1 = background[Int.random(in: background.indices)]
Background2 = background[Int.random(in: background.indices)]
Background3 = background[Int.random(in: background.indices)]
Background4 = background[Int.random(in: background.indices)]
Background5 = background[Int.random(in: background.indices)]
Background6 = background[Int.random(in: background.indices)]
Background7 = background[Int.random(in: background.indices)]
Background8 = background[Int.random(in: background.indices)]
Background9 = background[Int.random(in: background.indices)]
Background10 = background[Int.random(in: background.indices)]
Background11 = background[Int.random(in: background.indices)]
Background12 = background[Int.random(in: background.indices)]
}
func answerIsCorrect(answer: Int) {
let isCorrect = answer == correctAnswer ? true : false
if isCorrect {
self.score += 1
} else {
self.score -= 1
}
}
func generateAnswers() {
firstNumber = Int.random(in: 0...(difficulty/2))
secondNumber = Int.random(in: 0...(difficulty/2))
var answerList = [Int]()
correctAnswer = firstNumber + secondNumber
for i in 0...2 {
answerList.append(Int.random(in: 0...difficulty))
}
answerList.append(correctAnswer)
choiceArray = answerList.shuffled()
}
func didTap(button: CalcButton) {
switch button {
case .add, .subtract, .multiply, .divide, .equal:
if button == .add {
self.currentOperation = .add
self.runningNumber = Int(self.value) ?? 0
} else if button == .subtract {
self.currentOperation = .subtract
self.runningNumber = Int(self.value) ?? 0
} else if button == .multiply {
self.currentOperation = .multiply
self.runningNumber = Int(self.value) ?? 0
} else if button == .divide {
self.currentOperation = .divide
self.runningNumber = Int(self.value) ?? 0
} else if button == .equal {
let runningValue = self.runningNumber
let currentValue = Int(self.value) ?? 0
switch self.currentOperation {
case .add:
self.value = "\(runningValue + currentValue)"
case .subtract:
self.value = "\(runningValue - currentValue)"
case .multiply:
self.value = "\(runningValue * currentValue)"
case .divide:
self.value = "\(runningValue / currentValue)"
case .none:
break
}
}
if button != .equal {
self.value = "0"
}
case .clear:
self.value = "0"
case .decimal, .percent, .negative:
break
default:
let number = button.rawValue
if self.value == "0" {
value = number
} else {
self.value = "\(self.value)\(number)"
}
}
}
func buttonWidth(item: CalcButton) -> CGFloat {
if item == .zero {
return ((UIScreen.main.bounds.width - (4*12)) / 4) * 2
}
return (UIScreen.main.bounds.width - (5*12)) / 4
}
func buttonHeight() -> CGFloat {
return (UIScreen.main.bounds.height - (5*12)) / 4
}
}
struct AnswerButton: View {
var number: Int
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color.white.opacity(0.6))
.frame(maxWidth: .infinity, minHeight: 110)
.cornerRadius(15)
.padding([.leading, .trailing], 10)
.padding(.top)
.padding(.trailing, 6)
Text("\(number)")
.font(.largeTitle)
.fontWeight(.medium)
.foregroundColor(.primary)
.padding([.leading, .trailing], 10)
.padding(.top)
.padding(.trailing, 6)
}
}
}
enum CalcButton: String {
case one = "1"
case two = "2"
case three = "3"
case four = "4"
case five = "5"
case six = "6"
case seven = "7"
case eight = "8"
case nine = "9"
case zero = "0"
case add = "+"
case subtract = "-"
case divide = "÷"
case multiply = "×"
case equal = "="
case clear = "AC"
case decimal = "."
case percent = "%"
case negative = "+/-"
var buttonColor: Color {
switch self {
case .add, .subtract, .multiply, .divide, .equal:
return .orange
case .clear, .negative, .percent:
return .gray
default:
return Color(UIColor(red: 55/255.0, green: 55/255.0, blue: 55/255.0, alpha: 1))
}
}
}
enum Operation {
case add, subtract, multiply, divide, none
}
extension View {
func halfSheet<SheetView: View>(showSheet: Binding<Bool>, #ViewBuilder sheetView: #escaping () -> SheetView) -> some View {
return self
.background(
HalfSheetHelper(sheetView: sheetView(), showSheet: showSheet)
)
}
}
struct HalfSheetHelper<SheetView: View>: UIViewControllerRepresentable {
var sheetView: SheetView
#Binding var showSheet: Bool
class Coordinator {
let dummyController = UIViewController()
let sheetController: CustomHostingController<SheetView>
init(sheetView: SheetView, showSheet: Binding<Bool>) {
sheetController = CustomHostingController(rootView: sheetView, onDismiss: { showSheet.wrappedValue = false })
dummyController.view.backgroundColor = .clear
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(sheetView: sheetView, showSheet: $showSheet)
}
func makeUIViewController(context: Context) -> UIViewController {
return context.coordinator.dummyController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
context.coordinator.sheetController.rootView = sheetView
if showSheet && uiViewController.presentedViewController == nil {
uiViewController.present(context.coordinator.sheetController, animated: true)
}
}
}
class CustomHostingController<Content: View>: UIHostingController<Content> {
var onDismiss: (() -> Void)?
convenience init(rootView: Content, onDismiss: #escaping () -> Void) {
self.init(rootView: rootView)
self.onDismiss = onDismiss
}
override func viewDidLoad() {
if let presentationController = presentationController as? UISheetPresentationController {
presentationController.detents = [
.large(),
.medium()
]
presentationController.prefersGrabberVisible = true
}
}
override func viewDidDisappear(_ animated: Bool) {
onDismiss?()
}
}
struct QuestionView_Previews: PreviewProvider {
static var previews: some View {
QuestionView()
}
}
Thank you for looking at my code. I look forward to your answer!
struct ContentView: View {
#State private var progress: Double = 0.1
var body: some View {
VStack {
Circle()
.trim(from: 0, to: progress)
.stroke(lineWidth: 20)
.rotationEffect(Angle(degrees: 180))
.shadow(radius: 8)
.rotationEffect(.degrees(90))
.frame(width: 60, height: 60)
.padding()
Slider(value: $progress, in: 0...1)
.padding()
}
}
}

Swiftui animation calendar dates

In my app I want to show the passage of time by having a "calendar" transition from one date to the next, to the next, to the next, etc. So, for example, if I want to show the date transitioning from the 18th, to the 19th, to the 20th, I will show 18 for 1 second, then fade that out, fade in 19, fade that out, then fade in 20.
I have the following to show one date animating to the next (e.g. 18 > 19th):
struct Calendar: View {
#State var date: String
var body: some View {
VStack {
Spacer()
ZStack {
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
RoundedRectangle(cornerRadius: 20)
.fill(Color.red)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
Text(date).font(.system(size: 70.0))
.offset(y: 20)
}
Spacer()
Spacer()
}.padding()
}
}
and I call this in my code using:
ScrollView(showsIndicators: false) {
VStack {
Spacer()
ZStack {
if showseconddate == false {
Calendar(date: "18").animation(.easeInOut(duration: 1.0))
.transition(.opacity)
}
if showseconddate == true {
Calendar(date: "19").animation(.easeInOut(duration: 1.0))
.transition(.opacity)
}
Spacer()
}
}.onAppear {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
withAnimation(Animation.linear(duration: 0.5)) {
self.showseconddate.toggle()
self.showfirstdate.toggle() }
timer.invalidate()
}
}
}
This all works as intended, but I'm struggling to then expand this to a case where I want to show it transitioning through multiple dates, such as 18 > 19 >20 >21 etc. Does anyone know how to expand this, or to use an alternative solution? Any solution must fade out the old date, then fade in the new date. Many thanks!
Here's a relatively compact solution. Instead of relying on Bool values, it cycles through an array:
struct ContentView: View {
private var dates = ["18","19","20","21","22"]
#State private var dateIndex = 0
private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
var body: some View{
ScrollView(showsIndicators: false) {
VStack {
Spacer()
ZStack {
Calendar(date: dates[dateIndex])
.transition(.opacity)
.id("date-\(dateIndex)")
Spacer()
}
}.onReceive(timer) { _ in
var newIndex = dateIndex + 1
if newIndex == dates.count { newIndex = 0 }
withAnimation(.easeInOut(duration: 0.5)) {
dateIndex = newIndex
}
}
}
}
}
I had reworked your code to get the animations running, I felt it was a bit annoying to watch the entire calendar flash, so I reworked it into a CalendarPage (I renamed Calendar to CalendarPage because Calendar is a Type in Swift) and CalendarView that takes the date and overlays it on the page.
CalendarPage is your Calendar with the date var and Text() removed:
struct CalendarPage: View {
var body: some View {
VStack {
Spacer()
ZStack {
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
RoundedRectangle(cornerRadius: 20)
.fill(Color.red)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
}
Spacer()
Spacer()
}.padding()
}
}
CalendarView uses the timer to increment your dates until you reach the endDate, and it only effects the opacity of the date itself, not the whole calendar:
struct CalendarView: View {
#State var date: Int = 0
#State var animate = false
#State var calendarSize: CGFloat = 20
let endDate = 31
// This keeps the font size consistent regardless of the size of the calendar
var fontSize: CGFloat {
calendarSize * 0.45
}
private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
var body: some View {
CalendarPage(date: date.description)
.overlay(alignment: .bottom) {
VStack {
Text(date.description)
.font(.system(size: fontSize))
.opacity(animate ? 1 : 0)
}
.frame(height: calendarSize * 0.8)
}
.frame(width: 200, height: 200)
.readSize(onChange: { size in
calendarSize = min(size.width, size.height)
})
.onReceive(timer) { _ in
date += 1
withAnimation(.linear(duration: 0.3)) {
animate = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) {
if date != endDate {
withAnimation(.linear(duration: 0.2)) {
animate = false
}
} else {
timer.upstream.connect().cancel()
}
}
}
}
}
I also used a preference key to compute the height of the CalendarPage (though I could have hard coded it) using this View extension from FiveStars blog
extension View {
func readSize(onChange: #escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
fileprivate struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}

SwiftUI: Longpress Gesture Hold for only 1 Second

Using the Long press gestures on SwiftUI only keep the long press hold gesture for 1 second then automatically releases the long press. I would like for the user to press up to 1 minute or more. Is this possible and how can it be done.
Check out my code below, which currently only supports a 1-second duration long-press gesture.
struct IgnitionDriveView: View {
#GestureState private var drivingGestureState = false
#GestureState private var reverseGestureState = false
#State private var showDriveAlert = true
#State private var showOutOfGasAlert = false
#State var distanceCovered: Float = 1.0
var body: some View {
let circleShape = Circle()
let driveGesture = LongPressGesture(minimumDuration: 1)
.updating($drivingGestureState) { (currentState, gestureState, transaction) in
gestureState = currentState
}.onChanged { _ in
if distanceCovered < 1000 {
self.distanceCovered += 10
} else {
showOutOfGasAlert = true
}
}
let reverseGesture = LongPressGesture(minimumDuration: 1)
.updating($reverseGestureState) { (currentState, gestureState, transaction) in
gestureState = currentState
}.onChanged { _ in
if distanceCovered > 0 {
self.distanceCovered -= 10
}
}
VStack(alignment: .leading) {
Text("Distance Covered in Km: \(distanceCovered)")
.font(.headline)
ProgressView(value: distanceCovered > 0 ? distanceCovered : 0, total: 1000)
.frame(height: 40)
HStack {
ZStack {
circleShape.strokeBorder(style: StrokeStyle(lineWidth: 2))
circleShape
.fill(drivingGestureState ? .white : .red)
.frame(width: 100, height: 100, alignment: .center)
Text("D")
.bold()
.padding()
.foregroundColor(.green)
.font(.title)
}.foregroundColor(.green)
.gesture(driveGesture)
Spacer()
ZStack {
circleShape.strokeBorder(style: StrokeStyle(lineWidth: 2))
circleShape
.fill(reverseGestureState ? .white : .red)
.frame(width: 100, height: 100, alignment: .center)
Text("R")
.bold()
.padding()
.foregroundColor(.red)
.font(.title)
}.foregroundColor(.green)
.gesture(reverseGesture)
}.padding()
}.alert("Press D to Drive and R to Reverse", isPresented: $showDriveAlert) {
Button("Okay") { showDriveAlert = false }
}.alert("You ran out of Gas, Reverse to Gas Station", isPresented: $showOutOfGasAlert) {
Button("Sucks, but fine!") { showOutOfGasAlert = false }
}
.padding()
}
}
here is a very basic approach that you can build on, based on the code in:
https://adampaxton.com/make-a-press-and-hold-fast-forward-button-in-swiftui/
struct IgnitionDriveView: View {
#State private var timer: Timer?
#State var isLongPressD = false
#State var isLongPressR = false
#State private var showDriveAlert = true
#State private var showOutOfGasAlert = false
#State var distanceCovered: Float = 0.0
private func circleShape(isPressed: Binding<Bool>) -> some View {
Button(action: {
if isPressed.wrappedValue {
isPressed.wrappedValue.toggle()
timer?.invalidate()
}
}) {
ZStack {
Circle().strokeBorder(style: StrokeStyle(lineWidth: 2))
Circle().fill(isPressed.wrappedValue ? .white : .red)
}.frame(width: 100, height: 100, alignment: .center)
}
}
var body: some View {
VStack(alignment: .leading) {
Text("Distance Covered in Km: \(distanceCovered)").font(.headline)
ProgressView(value: distanceCovered > 0 ? distanceCovered : 0, total: 1000).frame(height: 40)
HStack {
ZStack {
circleShape(isPressed: $isLongPressD)
.simultaneousGesture(LongPressGesture(minimumDuration: 0.2).onEnded { _ in
isLongPressD = true
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
if distanceCovered < 1000 {
distanceCovered += 10
} else {
showOutOfGasAlert = true
}
})
})
Text("D").bold().padding().foregroundColor(.green).font(.title)
}.foregroundColor(.green)
Spacer()
ZStack {
circleShape(isPressed: $isLongPressR)
.simultaneousGesture(LongPressGesture(minimumDuration: 0.2).onEnded { _ in
isLongPressR = true
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
if distanceCovered > 0 {
distanceCovered -= 10
}
})
})
Text("R").bold().padding().foregroundColor(.blue).font(.title)
}.foregroundColor(.green)
}.padding()
}.alert("Press D to Drive and R to Reverse", isPresented: $showDriveAlert) {
Button("Okay") { showDriveAlert = false }
}.alert("You ran out of Gas, Reverse to Gas Station", isPresented: $showOutOfGasAlert) {
Button("Sucks, but fine!") { showOutOfGasAlert = false }
}
.padding()
}
}
The LongPressGesture is updating after the minimum time no matter if the user lifts its finger or not. Take a look here on how to register to the onEnded even which I guess is what you want to wait for. i.e when the user takes his/hers finger off screen - https://developer.apple.com/documentation/swiftui/longpressgesture

#ObservedObject not publishing changes to View

I have a View with a Text that is meant to be updated based on a CountDown() value secondsLeft:
struct TimerCapsule: View {
#ObservedObject var countdown = CountDown()
var body: some View {
Text("\(self.countdown.secondsLeft)")
.font(.system(size: 30))
.foregroundColor(.white)
.frame(width: 100, height: 40)
.padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
.background(
Capsule()
.fill(Color("Exit"))
.opacity(0.7)
)
.onAppear{
print("Start countdown")
self.countdown.start(from: 30)
}
.onDisappear {
self.countdown.stop()
}
}
}
Here is the CountDown class below. print("Seconds left: \(self.secondsLeft)") successfully prints 30, 29, 28 etc - however, this is not updated in the View above. There is only 1 instance been used in the view above so I'm not sure why it is not being reflected in the Text("\(self.countdown.secondsLeft)") above.
Timer:
class CountDown: ObservableObject {
#Published var secondsLeft = 30
#ObservedObject var block = Block()
var timer: Timer!
func start(from seconds: Int){
self.secondsLeft = seconds
print("start timer")
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ _ in
if (self.secondsLeft == 1){
switch self.block.type {
case .match:
self.block.pass()
case .pass:
self.block.pass()
default:
self.block.delete()
}
}
self.secondsLeft -= 1
print("Seconds left: \(self.secondsLeft)")
}
}
func stop(){
self.timer?.invalidate()
}
}
Any idea?
EDIT:
ContentView.swift
struct ContentView: View {
#ObservedObject var auth = UserAuth()
var body: some View {
Group {
if auth.uid != nil { HomeView(auth: auth) } else { LoginView(auth: auth) }
}
}
}
HomeView.swift
struct HomeView: View {
let auth: UserAuth
#ObservedObject var block = Block()
init(auth: UserAuth) {
self.auth = auth
UITabBar.appearance().barTintColor = UIColor.orange
UITabBar.appearance().unselectedItemTintColor = UIColor.white.withAlphaComponent(0.5)
UITabBar.appearance().autoresizesSubviews = true
}
#State var selectedTab = 1
#ObservedObject var locationManager = LocationManager()
var body: some View {
ZStack {
TabView(selection: $selectedTab) {
Settings(auth: auth)
.tabItem {
Image(systemName: "gear")
Text("Settings")
}.tag(0)
ZStack {
MapView(locationManager: locationManager)
Color.black.opacity(self.block.status.opacity).animation(nil)
VStack {
if self.block.type == Type.match || self.block.type == Type.pass {
VStack{
TimerCapsule()
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
.padding(.top, 20)
}
Spacer()
if self.block.type == Type.create || self.block.type == Type.delete || self.block.type == Type.pass{
StatusButton()
}
}.padding(40)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}.edgesIgnoringSafeArea(.top)
.tabItem {
Image(systemName: "location.circle.fill")
Text("Home")
}.tag(1)
ProfileView()
.tabItem {
Image(systemName: "person")
Text("Profile")
}.tag(2)
}.accentColor(Color.white)
.font(.headline)
}
}
}
Can't reproduce any issue. You didn't give enough information to allow the entire code to compile, but if we remove references to all the stuff you left out, we get this:
class CountDown: ObservableObject {
#Published var secondsLeft = 30
var timer: Timer!
func start(from seconds: Int){
self.secondsLeft = seconds
print("start timer")
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ _ in
// don't know what Block is
self.secondsLeft -= 1
print("Seconds left: \(self.secondsLeft)")
}
}
func stop(){
self.timer?.invalidate()
}
}
struct ContentView: View {
#ObservedObject var countdown = CountDown()
var body: some View {
Text("\(self.countdown.secondsLeft)")
.font(.system(size: 30))
.foregroundColor(.white)
.frame(width: 100, height: 40)
.padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
.background(
Capsule()
.fill(Color(.black)) // don't know what Exit is
.opacity(0.7)
)
.onAppear{
print("Start countdown")
self.countdown.start(from: 30)
}
.onDisappear {
self.countdown.stop()
}
}
}
Copy and paste that into a new SwiftUI project and you'll see that the text does count down.
So if something is preventing that from happening, it's something you didn't show.

Resources