Alarm Button interacts incorrectly with view - SwiftUI - swift

I'm continuing to develop a timer app for practice purposes and have the basic functions ready so far.
The timer works in such a way that if you click on "Start" the timer simply runs down to 0 and then selects the next player - here you can also the button in the upper middle whether an alarm is played after the timer or not - however, this also stops the timer, although this does not occur in the implementation (see also video below). I hope someone can help me.
For the timer I made a StopWatchManager class:
import Foundation
class StopWatchManager : ObservableObject {
#Published var secondsElapsed : Double = 0.00
#Published var mode : stopWatchMode = .stopped
var timer : Timer = Timer()
//Start the timer
func start() -> Void {
secondsElapsed = 0.00
mode = .running
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true){ _ in
self.secondsElapsed += 0.01
}
}
//Pause the timer
func pause() -> Void {
timer.invalidate()
mode = .paused
}
//Stop the timer
func stop() -> Void {
timer.invalidate()
secondsElapsed = 0.00
self.mode = .stopped
}
}
enum stopWatchMode{
case running
case stopped
case paused
}
Then I have an overview TimerView which implements some little details and connects the buttons with TimerTextView:
import SwiftUI
struct TimerView: View {
#State private var alarmSoundOn : Bool = true
#State private var quitGame : Bool = false
//buttons variable for timer
#State private var startTimer : Bool = false
#State private var resetTimer : Bool = false
#State private var pauseTimer : Bool = false
#State private var quitTimer : Bool = false
var body: some View {
ZStack{
Color.blue.ignoresSafeArea()
TimerTextView(startTimer: $startTimer, resetTimer: $resetTimer, pauseTimer: $pauseTimer, quitTimer: $quitTimer, playAlarmSound: $alarmSoundOn)
if (!quitGame) {
VStack{
HStack(alignment: .top){
//TODO - find bug with Timer
Button(action: {
alarmSoundOn.toggle()
}, label: {
if (alarmSoundOn) {
Image(systemName: "speaker.circle")
.resizable().frame(width: 30, height: 30)
} else {
Image(systemName: "speaker.slash.circle")
.resizable().frame(width: 30, height: 30)
}
}).foregroundColor(.white)
}
Spacer()
}
}
}
}
}
And here is my TimerTextView where all the logic with the buttons and the circle happens:
import SwiftUI
import AVFoundation
struct TimerTextView: View {
//timer instance
#ObservedObject var playTimer : StopWatchManager = StopWatchManager()
//default buttons for the timer
#Binding var startTimer : Bool
#Binding var resetTimer : Bool
#Binding var pauseTimer : Bool
#Binding var quitTimer : Bool
//variables for circle
var timerSeconds : Int = 10
#State private var progress : Double = 1.00
#State private var endDate : Date? = nil
//seconds of timer as text in the middle of the timer
#State var textTimerSeconds : Double = 10
//sound for the alarm after the timer exceeded
#Binding var playAlarmSound : Bool
#State private var playAlarmAtTheEnd : Bool = true
let sound : SystemSoundID = 1304
var body: some View {
ZStack{
if (quitTimer) {
EmptyView()
} else {
//Circles---------------------------------------------------
VStack{
VStack{
VStack{
ZStack{
//Circle
ZStack{
Circle()
.stroke(lineWidth: 20)
.foregroundColor(.white.opacity(0.3))
Circle()
.trim(from: 0.0, to: CGFloat(Double(min(progress, 1.0))))
.stroke(style: StrokeStyle(lineWidth: 20.0, lineCap: .round, lineJoin: .round))
.rotationEffect(.degrees(270.0))
.foregroundColor(.white)
.animation(.linear, value: progress)
}.padding()
VStack{
//Timer in the middle
Text("\(Int(textTimerSeconds.rounded(.up)))")
.font(.system(size: 80))
.bold()
.multilineTextAlignment(.center)
.foregroundColor(.white)
}
}.padding()
//timer responds every milliseconds and calls intern function decrease timer
.onReceive(playTimer.$secondsElapsed, perform: { _ in
decreaseTimer()
})
//pauses the timer
.onChange(of: pauseTimer, perform: { change in
if (pauseTimer) {
playTimer.pause()
} else {
endDate = Date(timeIntervalSinceNow: TimeInterval(textTimerSeconds))
playTimer.start()
}
})
//resets the timer
.onChange(of: resetTimer, perform: { change in
if (resetTimer) {
resetTimerToBegin()
resetTimer = false
}
})
//play alarm sound at the end of the timer
.onChange(of: playAlarmSound, perform: { change in
if (playAlarmSound){
playAlarmAtTheEnd = true
} else {
playAlarmAtTheEnd = false
}
})
.onChange(of: startTimer, perform: { change in startTimerFromBegin()
})
}
}.foregroundColor(Color.black)
//----------------- Buttons
VStack(spacing: 50){
//if isStoped -> show play, reset & quit
//if !isStoped -> show pause
if(pauseTimer){
HStack(alignment: .bottom){
Spacer()
Spacer()
//Play Button
Button(action: {
pauseTimer = false
}, label: {
VStack(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, spacing: 5){
Image(systemName: "play.fill")
Text("Play").font(.callout)
}
})
.font(.title2)
.frame(width: 60, height: 60, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.padding(3.0)
.buttonStyle(BorderlessButtonStyle())
Spacer()
//Reset Button
Button(action: {
resetTimer = true
}, label: {
VStack(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, spacing: 5){
Image(systemName: "gobackward")
Text("Reset").font(.callout)
}
})
.font(.title2)
.frame(width: 60, height: 60, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.padding(3.0)
.buttonStyle(BorderlessButtonStyle())
Spacer()
//Quit Button
Button(action: {
quitTimer = true
}, label: {
VStack(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, spacing: 5){
Image(systemName: "flag.fill")
Text("Exit").font(.callout)
}
})
.font(.title2)
.frame(width: 60, height: 60, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.padding(3.0)
.buttonStyle(BorderlessButtonStyle())
Spacer()
Spacer()
}
} else if (startTimer) {
//Pause Button
Button(action: {
pauseTimer = true
}, label: {
VStack(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, spacing: 5){
Image(systemName: "pause.fill")
Text("Pause").font(.callout)
}
})
.font(.title2)
.frame(width: 60, height: 60, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.padding(3.0)
.buttonStyle(BorderlessButtonStyle())
} else {
//Play Button
Button(action: {
pauseTimer = false
startTimer = true
}, label: {
VStack(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, spacing: 5){
Image(systemName: "play.fill")
Text("Start").font(.callout)
}
})
.font(.title2)
.frame(width: 60, height: 60, alignment: .center)
.padding(3.0)
.buttonStyle(BorderlessButtonStyle())
}
}.foregroundColor(.white)
}
}
}
}
//FUNCTIONS --------------------------------
private func startTimerFromBegin() -> Void{
endDate = Date(timeIntervalSinceNow: TimeInterval(timerSeconds))
playTimer.start()
}
private func resetTimerToBegin() -> Void {
endDate = Date(timeIntervalSinceNow: TimeInterval(timerSeconds))
progress = 1.0
textTimerSeconds = Double(timerSeconds)
}
private func decreaseTimer() -> Void{
guard let endDate = endDate else { print("decreaseTimer() was returned"); return }
progress = max(0, endDate.timeIntervalSinceNow / TimeInterval(timerSeconds))
textTimerSeconds -= 0.01
if endDate.timeIntervalSinceNow <= 0 {
if (playAlarmAtTheEnd) {
AudioServicesPlayAlertSound(sound)
}
}
}
}

You are instantiating your playTimer in the subview ... That will reset it when the view is redrawn. Instead you should do this in the parent view and pass it down.
Also you should use #StateObject to instantiate.
struct TimerView: View {
//timer instance HERE
#StateObject var playTimer : StopWatchManager = StopWatchManager()
...
pass down to subview
// pass timer here
TimerTextView(playTimer: playTimer, startTimer: $startTimer, resetTimer: $resetTimer, pauseTimer: $pauseTimer, quitTimer: $quitTimer, playAlarmSound: $alarmSoundOn)
Subview:
struct TimerTextView: View {
//passed timer
#ObservedObject var playTimer : StopWatchManager
...
One more thing: Firing the timer every 0.01 seconds is too much. You should go to 0.1

Related

I'm trying to create an animation for prompts in a search bar like in Instagram

This is what my code creates
I want the prompts to keep moving up one after the other in a continuous cycle like on Instagram but am not able to move the text up a second time and it comes back down. Is it possible to have two offset modifiers for the same text so that after moving up into the bar I can move it up a second time so it moves out of the view?
#State private var list: [String] = ["Search \"puppy biting\"","Search \"deworming\""]
#State private var currentPrompt:Int = 0
#State private var timer: Timer?
#State private var playAnimation : Bool = false
#State private var playAnimation2 : Bool = false
ZStack {
VStack {
HStack {
if !list.isEmpty {
HStack {
ZStack {
Text(list[0])
.foregroundColor(.gray)
.font(.fontFor(.regular, size:
.frame(alignment: .leading)
.offset(y: playAnimation ? 30:0)
.opacity(playAnimation ? 0:1)
Text(list[1])
.foregroundColor(.gray)
.font(.fontFor(.regular, size:
.frame(alignment: .leading)
.offset(y: playAnimation2 ? 30:0)
.opacity(playAnimation ? 1:0)
}
Spacer()
}
.frame(height: 20, alignment: .leading)
Spacer()
}
}.padding(.horizontal)
.padding(.vertical,10)
.background(Color("searchBg"))
.cornerRadius(30)
}
.padding(.horizontal, 20)
.padding(.bottom, 20)
.background(Color.white.ignoresSafeArea(edges: .top))
}
.onAppear {
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { timer in
withAnimation(.easeInOut.delay(1)) {
playAnimation.toggle()
if playAnimation {
playAnimation2.toggle()
}
if playAnimation2 {
playAnimation3.toggle()
}
}
}
}

How to remove the cornerradius of sheets in swiftui?

Is there a way to remove the cornerRadius of a sheet? I tried it like this:
.sheet(isPresented: $showModal) {
Modal().cornerRadius(0, corners: [.topLeft, .topRight])
}
but it didn't work.
I know I can just use fullScreenCover but I still want to know if there is a solution to this.
According to my comment above you can create your own slide-in menu.
In the example below I added a close button as well as gesture control to close the view.
//
//
// SlideInMenu.swift
// SlideInMenu
//
// Created by Sebastian on 21.09.22.
//
import SwiftUI
var bounds = UIScreen.main.bounds
struct ContentView: View {
#State var selectedItem: String = ""
#State var showMenu = false
var body: some View {
ZStack() {
MainView(selectedItem: $selectedItem, showMenu: $showMenu)
.blur(radius: showMenu ? 3 : 0)
SlideView(selectedItem: $selectedItem, showMenu: $showMenu)
}
.edgesIgnoringSafeArea(.all)
}
}
struct MainView: View {
#Binding var selectedItem: String
#Binding var showMenu: Bool
var body: some View {
HStack(){
Spacer()
VStack() {
Spacer()
Text("This is your main View")
.foregroundColor(.white)
.padding()
Button(action: {
withAnimation(.linear(duration: 0.3)) {
self.showMenu.toggle()
}
}) {
Text("Show Menu")
.font(.system(size: 20, weight: .medium))
.foregroundColor(.white)
}
Spacer()
}
Spacer()
}.background(Color.blue)
}
}
struct SlideView: View {
#Binding var selectedItem: String
#Binding var showMenu: Bool
#State private var viewOffest: CGFloat = 100
#State private var offset = CGSize.zero
#State private var isDragging = false
var body: some View {
let dragGesture = DragGesture()
.onChanged { value in
withAnimation(.linear(duration: 0.2)) {
if value.translation.height >= 0 {
offset = value.translation
}
}
}
.onEnded { _ in
withAnimation(.linear(duration: 0.2)) {
isDragging = false
if offset.height > (bounds.height - viewOffest)/3 {
showMenu.toggle()
}
offset = .zero
}
}
ZStack() {
Color.black
.opacity(showMenu ? 0.5 : 0)
VStack() {
HStack() {
Spacer()
Spacer()
}
VStack(alignment: .leading) {
HStack() {
Spacer()
Text("Here is the menu")
.foregroundColor(.black)
Spacer()
}
HStack() {
Spacer()
Button(action: {
withAnimation(.linear(duration: 0.3)) {
self.showMenu.toggle()
}
}) {
Text("Close Menu")
.font(.system(size: 20, weight: .medium))
.foregroundColor(.red)
}
.padding()
Spacer()
}
Spacer()
}
.padding()
.background(Color.white)
.cornerRadius(0)
}
.offset(y: showMenu ? viewOffest + offset.height : bounds.height)
.gesture(dragGesture)
}
}
}
It is not possible for now perhaps we can in further update, nonetheless you can create your own custom view.

iOS | SwiftUI | Undesirable result to exchange items between TimerView and TaskView through ContentView, #State isn't delivering results

I have three Views exchanging information, ContentView, TimerView and TaskView. I used #Binding in TaskView to bring data from TaskView to ContentView, now I want to use that data to pass into TimerView.
I created a #State variable in ContentView to store the data from TaskView, but when I try to use that #State variable to pass data to TimerView it isn't giving me any desirable results.
Now if you see the TasksView call in ContentView, I get the output for the print statement, but in the main TimerView, it doesn't change from the default I set as #State variable in timerInfo.
Any help is appreciated, thank you.
Code for ContentView ->
//
// ContentView.swift
// ProDActivity
//
// Created by Vivek Pattanaik on 5/27/21.
//
import SwiftUI
struct TimerInfo : Identifiable {
let id = UUID()
// let taskIndex : Int
// let taskProjectName : String
var timerTaskName : String
var timerMinutes : Float
let timerIntervals : Int
var timerPriority : String
var timerShortbreakMinute : Float
var timerLongbreakMinute : Float
var timerLongbreakInterval : Int
}
struct ContentView: View {
init() {
UITabBar.appearance().backgroundColor = UIColor.init(Color("TabBar "))
}
// #State var selection: Tab = .dasboard
#State var timerInfo = TimerInfo(timerTaskName: "Sample Task 1", timerMinutes: 30, timerIntervals: 10, timerPriority: "High Priority", timerShortbreakMinute: 5, timerLongbreakMinute: 15, timerLongbreakInterval: 3)
var body: some View {
TabView {
TimerView(defaultTimeRemaining: self.timerInfo.timerMinutes * 60, timeRemaining: self.timerInfo.timerMinutes * 60)
.tabItem {
Image(systemName: "clock.fill")
Text("Timer")
}.tag(0)
TasksView(didClickTimer: { info in
self.timerInfo.timerTaskName = info.timerTaskName
self.timerInfo.timerMinutes = info.timerMinutes
self.timerInfo.timerPriority = info.timerPriority
self.timerInfo.timerShortbreakMinute = info.timerShortbreakMinute
self.timerInfo.timerLongbreakMinute = info.timerLongbreakMinute
self.timerInfo.timerLongbreakInterval = info.timerLongbreakInterval
print("\(self.timerInfo.timerMinutes)ContentView")
})
.tabItem {
Image(systemName: "doc.plaintext.fill")
Text("Tasks")
}.tag(1)
StatisticsView()
.tabItem {
Image(systemName: "chart.pie.fill")
Text("Statistics")
}.tag(3)
SettingsView()
.tabItem {
Image(systemName: "gearshape.fill")
Text("Settings")
}.tag(4)
}
.font(.headline)
.accentColor(Color("AccentColor"))
.environment(\.colorScheme, .dark)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Code for TasksView ->
//
// TasksView.swift
// ProDActivity
//
// Created by Vivek Pattanaik on 5/30/21.
//
import SwiftUI
struct TaskLabels : Identifiable {
let id = UUID()
// let taskIndex : Int
// let taskProjectName : String
let taskName : String
let taskPriority : String
let taskIntervals : String
let taskMinutes : String
let shortBreakMinutes : String
let longBreakMinutes : String
let longBreakIntervals : String
}
struct TaskRow: View {
let tasks : TaskLabels
var body: some View {
HStack(alignment: .center, spacing: 10) {
Image(tasks.taskPriority)
.frame(width: 40, height: 40, alignment: .center)
VStack(alignment: .leading, spacing:4){
Text(tasks.taskName)
.font(.system(size: 15))
Text("\(tasks.shortBreakMinutes) Min Breaks")
.font(.system(size: 13))
}
.frame(width: 100, height: 20, alignment: .leading)
Spacer()
VStack(alignment: .trailing, spacing:4){
Text("0/\(tasks.taskIntervals)")
Text("\(tasks.taskMinutes) Min Tasks")
.font(.system(size: 13))
}
.frame(width: 90, height: 20, alignment: .trailing)
.padding()
}
}
}
struct TasksView: View {
// #Binding var timeSelected : Float
#State var addTasksModalView: Bool = false
#State var taskLabels : [TaskLabels] = []
var didClickTimer : (TimerInfo) -> ()
var body: some View {
NavigationView{
if taskLabels.count != 0 {
List{
ForEach(taskLabels) { task in
HStack {
Button(action: {
self.didClickTimer(.init(timerTaskName: task.taskName, timerMinutes: Float(task.taskMinutes)!, timerIntervals: Int(task.taskIntervals)!, timerPriority: task.taskPriority, timerShortbreakMinute: Float(task.shortBreakMinutes)!, timerLongbreakMinute: Float(task.longBreakMinutes)!, timerLongbreakInterval: Int(task.longBreakIntervals)!))
print(task.taskMinutes)
}, label: {
TaskRow(tasks: task)
})
}
}
.onDelete(perform: self.deleteRow)
}
.navigationBarTitle("Tasks")
.navigationBarItems(trailing: Button(action: {
self.addTasksModalView = true
}, label: {
Image(systemName: "plus.square.on.square")
.resizable()
.frame(width: 26, height: 26, alignment: .leading)
.foregroundColor(Color.accentColor)
}))
.sheet(isPresented: $addTasksModalView, content: {
AddTasks(addTaskPresented: $addTasksModalView) { tasks in
taskLabels.append(tasks)
}
})
} else {
Text("")
.navigationTitle("Tasks")
.navigationBarTitle("Tasks")
.navigationBarItems(trailing: Button(action: {
self.addTasksModalView = true
}, label: {
Image(systemName: "plus.square.on.square")
.resizable()
.frame(width: 26, height: 26, alignment: .leading)
.foregroundColor(Color.accentColor)
}))
.sheet(isPresented: $addTasksModalView, content: {
AddTasks(addTaskPresented: $addTasksModalView) { tasks in
taskLabels.append(tasks)
}
})
}
}
.environment(\.colorScheme, .dark)
}
private func deleteRow(at indexSet: IndexSet){
self.taskLabels.remove(atOffsets: indexSet)
}
}
//struct TasksView_Previews: PreviewProvider {
// static var previews: some View {
// TasksView()
// }
//}
TimerView Code ->
//
// TimerView.swift
// ProDActivity
//
// Created by Vivek Pattanaik on 5/27/21.
//
import SwiftUI
struct TimerView: View {
let lineWidth : CGFloat = 11
let radius : CGFloat = 150
// to pass into the struct
// #State var taskTime = 300
#State var defaultTimeRemaining : Float
#State var timeRemaining : Float
#State private var isActive = false
#State private var showButtons = false
#State private var stopAlert = false
#State private var pausePressed = false
#State private var stopPressed = false
// #State private var timeRemainingSeconds : CGFloat = 25
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
// self.defaultTimeRemaining = timerInfo.timerMinutes
VStack(spacing : 60) {
ZStack{
RoundedRectangle(cornerRadius: 7)
.frame(width: 300, height: 70)
.foregroundColor(Color("TabBar "))
}
ZStack(alignment: Alignment(horizontal: .center, vertical: .center)) {
Circle()
.stroke(Color.gray, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
.opacity(0.2)
Circle()
.trim(from: 1 - CGFloat(((defaultTimeRemaining-timeRemaining)/defaultTimeRemaining)), to: 1 )
.stroke(Color.accentColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
.rotationEffect( .degrees(-90))
.animation(.easeInOut)
// VStack for the timer and seesions
VStack {
// Text("\(Int(timeRemaining)):\(Int(timeRemainingSeconds))")
Text("\(timeString(time: Int(timeRemaining)))")
.font(.system(size: 50)).fontWeight(.medium)
Text("0 of 5 Sessions")
.font(.system(size: 20)).fontWeight(.medium)
}
}.frame(width: radius*2, height: radius*2)
// BEGIN, STOP, PAUSE BUTTONS
HStack(spacing: 25){
if showButtons == false {
Button(action: {
}, label: {
ZStack {
Rectangle()
.frame(width: 176, height: 55, alignment: .center)
.foregroundColor(Color.accentColor)
.cornerRadius(5)
Button(action: {
self.showButtons.toggle()
isActive.toggle()
}, label: {
Text("BEGIN")
.foregroundColor(Color.white)
.font(.system(size: 23).weight(.medium))
.frame(width: 176, height: 55, alignment: .center)
})
}
})
} else if showButtons == true {
HStack {
ZStack {
Rectangle()
.frame(width: 152, height: 55, alignment: .center)
.foregroundColor(Color("LightDark"))
.cornerRadius(5)
.border(Color.accentColor, width: 2)
Button(action: {
self.stopPressed.toggle()
self.pausePressed = true
if isActive == true {
isActive.toggle()
self.stopAlert = true
} else {
self.stopAlert = true
}
}, label: {
Text("STOP")
.foregroundColor(Color.accentColor)
.font(.system(size: 23).weight(.medium))
.frame(width: 152, height: 55, alignment: .center)
})
.alert(isPresented: $stopAlert) {
Alert(title: Text("Are you sure you want to stop?"),
message: Text("This will stop the timer and task associated with it."),
primaryButton: .destructive(Text("Yes"), action: {
self.showButtons = false
timeRemaining = defaultTimeRemaining
}),
secondaryButton: .cancel({
isActive = false
pausePressed = true
})
)
}
}
ZStack {
Rectangle()
.frame(width: 152, height: 55, alignment: .center)
.foregroundColor(pausePressed ? Color.accentColor : Color("LightDark"))
.cornerRadius(5)
.border(pausePressed ? Color.accentColor : Color.accentColor, width: 2)
Button(action: {
pausePressed.toggle()
if pausePressed == true {
isActive = false
} else {
isActive = true
}
}, label: {
Text("\(pausePressed ? "RESUME" : "PAUSE")")
.foregroundColor(pausePressed ? Color("TabBar ") : Color.accentColor)
.font(.system(size: 23).weight(.medium))
.frame(width: 152, height: 55, alignment: .center)
})
}
}
}
}
}.onReceive(timer, perform: { _ in
guard isActive else {return}
if timeRemaining > 0 {
timeRemaining -= 1
} else {
isActive = false
showButtons = false
self.timer.upstream.connect().cancel()
}
})
}
func timeString(time: Int) -> String {
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60
return String(format:"%02i:%02i", minutes, seconds)
}
}
//struct TimerView_Previews: PreviewProvider {
// static var previews: some View {
// TimerView()
// }
//}
//func buttonChange(){
//
//}

SwiftUI + Timer + AVPlayer - When audio playing, onReceive(timer) not triggered

I am having issue to make my timer (and my animation) to work properly. My timer is not triggered if AVPlayer is currently playing. As soon as I pause it, my timer is resuming...
I have an implementation of a SlidingText view. Basically just moving a text in its box from left to right and then right to left. This animation is triggered with a Timer every 5 seconds.
I have an AVPlayer playing something at the same time with a pause/play button.
This is the implementation of the SlidingText. And the AVPlayer is just being called with a button that triggers player.play() or player.pause().
import SwiftUI
struct SlidingText: View {
let geometryProxy: GeometryProxy
#Binding var text: String
let font: Font
#State private var animateSliding: Bool = false
private let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
private let slideDuration: Double = 3
var body: some View {
ZStack(alignment: .leading, content: {
VStack(content: {
Text(text)
.font(self.font)
.foregroundColor(.white)
.background(Color.red)
})
.id("SlidingText-Animation")
.fixedSize(horizontal: false, vertical: true)
.frame(width: geometryProxy.size.width, alignment: animateSliding ? .trailing : .leading)
.clipped()
.animation(Animation.linear(duration: slideDuration))
.onReceive(timer, perform: { _ in
self.animateSliding.toggle()
})
})
.frame(width: self.geometryProxy.size.width, height: self.geometryProxy.size.height)
.background(Color.yellow)
}
}
Is there anything wrong it the way I use the timer here?
Thanks for any future help!
Alright, I endup not using a timer because it is buggy.
for those interested it looks like this now, I am using the Animation properties.
No more interferences with the audio player.
struct SlidingText: View {
let geometryProxy: GeometryProxy
#Binding var text: String
let font: Font
#State private var animateSliding: Bool = false
private let slideDelay: Double = 3
private let slideDuration: Double = 6
private var isTextLargerThanView: Bool {
if text.size(forWidth: geometryProxy.size.width, andFont: font).width < geometryProxy.size.width {
return false
}
return true
}
var body: some View {
ZStack(alignment: .leading, content: {
VStack(content: {
Text(text)
.font(self.font)
.foregroundColor(.white)
.background(Color.red)
})
.id("SlidingText-Animation")
.fixedSize()
.animation(Animation.linear(duration: slideDuration).delay(slideDelay).repeatForever(autoreverses: true))
.frame(width: geometryProxy.size.width,
alignment: isTextLargerThanView ? (animateSliding ? .trailing : .leading) : .center)
.onAppear(perform: {
self.animateSliding.toggle()
})
})
.clipped()
}
}

SwiftUI NSManagedObject changes do not update the view

In my view model, if I update an NSManagedObject property, then the view won't update anymore. I have attached the code and the view model.
I added a comment in front of the line that breaks the view update.
class StudySessionCounterViewModel: ObservableObject {
fileprivate var studySession: StudySession
init(_ studySession: StudySession) {
self.studySession = studySession
}
#Published var elapsedTime = 14 * 60
#Published var circleProgress: Double = 0
var timer: Timer?
var formattedTime: String {
get {
let timeToFormat = (Int(studySession.studyTime) * 60) - elapsedTime
let minutes = timeToFormat / 60 % 60
let seconds = timeToFormat % 60
return String(format:"%02i:%02i", minutes, seconds)
}
}
func startTimer() {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerTicked), userInfo: nil, repeats: true)
studySession.isActive = true //Adding this stops my view from updating
}
#objc func timerTicked() {
elapsedTime += 1
circleProgress = (Double(elapsedTime) / Double(studySession.studyTime * 60))
}
func stop() {
timer?.invalidate()
}
}
This is the view that uses that view model. When adding that line, the text that represents the formatted time no longer changes and the progress circle's progress remain the same.
If I remove the line, everything updates and work as expected.
struct StudySessionCounterView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var studySessionCounterVM: StudySessionCounterViewModel
var studySession: StudySession
init(_ studySession: StudySession) {
studySessionCounterVM = StudySessionCounterViewModel(studySession)
self.studySession = studySession
}
#State var showAlert = false
#State var isCounting = false
var body: some View {
VStack {
ZStack {
Text(studySessionCounterVM.formattedTime)
.font(.largeTitle)
ProgressRingView(size: .large, progress: $studySessionCounterVM.circleProgress)
}
Spacer()
if isCounting {
Button(action: {
self.studySessionCounterVM.stop()
self.isCounting = false
}) {
Image(systemName: "stop.circle")
.resizable()
.frame(width: 64, height: 64, alignment: .center)
.foregroundColor(.orange)
}
} else {
Button(action: {
self.studySessionCounterVM.startTimer()
self.isCounting = true
}) {
Image(systemName: "play.circle")
.resizable()
.frame(width: 64, height: 64, alignment: .center)
.foregroundColor(.orange)
}
}
}.padding()
.navigationBarTitle("Matematica", displayMode: .inline)
}
}
UPDATE: Found out that each time the NSManagedObject changes a property, the view model gets reinitialised. Still no solution, unfortunately