I have a workout timer that lets the user workout for 30 seconds & then break for 10 seconds. I have some visual aspects that represent this with some Text saying whether it's a break or a workout time. When the timer first starts it works good, but over time it drifts off & the text begins to be changed early then it should. I'm invalidating my timers once they are done, I can't see why I would be getting different results just because time has passed.
func start() {
centreText = "Workout"
Timer.scheduledTimer(withTimeInterval: 30, repeats: false) { timer in
timer.invalidate()
break()
}
}
func break() {
breatheText = "Break"
Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { timer in
timer.invalidate()
start()
}
}
How I'm calling start:
.onAppear {
withAnimation(.workout(workDuration: 30, break: 10), {
start()
})
Workout Animation:
public static func workout(workDuration: Double, break: Double) -> Animation {
return Animation.easeInOut(duration: workDuration).delay(break).repeatForever(autoreverses: true)
}
As views in SwiftUI are structs, it's best to use a configurator class using ObservableObject to update UI. Handle all the timers in this class and use a Published variable that the view assigns to.
struct MyView: View {
#StateObject private var config = MyViewConfig()
var body: some View {
Text(config.message)
Button("Start", action: config.start)
}
}
private final class MyViewConfig: ObservableObject {
#Published private(set) var message = "Workout"
func start() {
// star timer and update message
message = "Workout"
}
func stop() {
// stop timer and update message
message = "Break"
}
}
Related
I saw many blogs/articles on how to build a timer with Swift UI. But I cannot figure out to get what I really want working.
The 2 main issues I am facing are:
1. My TimerView is rebuilt whenever its parent view is rebuilt due to states changing
2. I am not able to send parameters to my #ObservedObject TimeCountDown property (2 parameters: duration coming from another view, and an onEnded completion)
class Round: ObservableObject {
#Publishedvar items: [Item]
var duration: Double
init(items: [Item], duration: Double) {
self.items = items
self.duration = duration
}
}
Struct ParentView: View {
#ObservedObject var round: Round
#State private var isRoundTerminated: Bool = false
var body: Some View {
VStack {
if isRoundTerminated {
RoundEndView()
} else {
TimerView(duration: round.duration, onEnded: onTimerTerminated)
RoundPlayView(items: round.items)
}
}
}
}
struct TimerView: View {
#ObservedObject countdown = TimeCountDown()
var duration: Double
var onEnded: (() -> Void)?
///// I DO NOT KNOW HOW TO PROPAGATE THE onEnded completion TO countdown:TimeCountDown
var body: Some View {
Text("There are \(countdown.remainingTime) remaining secs")
.onAppend() {
timer.start(duration: duration)
/// MAYBE I COULD ADD THE onEnded WITHIN THE start() CALL?
}
}
}
class TimeCountDown : ObservableObject {
var timer : Timer!
#Published var remainingTime: Double = 60
var onEnded: (() -> Void)?
init(onEnded: #escaping (() -> Void)?) {
self.onEnded = onEnded
}
func start(duration: Double) {
self.timer?.invalidate()
self.remainingTime = duration
timer = Timer.scheduledTimer(timeInterval:1, target: self, selector:#selector(updateCountdown), userInfo: nil, repeats: true)
}
#objc private func updateCount() {
remainingTime -= 1
if remainingTime <= 0 {
killTimer()
self.onEnded?()
}
}
private func killTimer() {
timer?.invalidate()
timer = nil
}
}
However that does not work...
I also tried to implement the following TimerView:
struct CountdownView: View {
#State private var remainingTime: Int = 60
#Binding var countingDown: Bool
var onEnded: (() -> Void)?
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
init(durations: TimerDuration, onEnded: (() -> Void)?, start: Binding<Bool>) {
self.onEnded = onEnded
self._countingDown = start
self.remainingTime = durations.duration
}
var body: some View {
Text("Remaining \(remainingTime) secs")
.onReceive(timer) {_ in
if self.countingDown {
if self.remainingTime > 0 {
self.remainingTime -= 1
} else {
self.onTerminated()
}
}
}
}
func onTerminated() {
timer.upstream.connect().cancel()
self.remainingTime = 0
onEnded?()
}
}
However when the ParentView is rebuilt very often (due to modifications to round.items (#Published from Round:ObservableObject) the timer can be frozen.
#George_E, please find below my update code (Please note that I simplified the nextWord() func (a but useless here) but it does modify the round.remainingDeck which is a Published property within the ObervableObject Round. That triggers a rebuild of RoundPlayView and that freezes the timer):
import SwiftUI
import Combine
class Round: ObservableObject {
#Published var remainingDeck: [String] = ["Round#1", "Round#2", "Round#3", "Round4"]
var roundDuration: Int = 5
}
struct ContentView: View {
#ObservedObject var round = Round()
var body: Some View {
RoundPlayView(round: round)
}
}
struct RoundPlayView: View {
#ObservedObject var round: Round
var duration: Int {
return round.roundDuration
}
#State private var timerStart: Bool = true
#State private var isTerminated: Bool = false
init(round: Round) {
self.round = round
}
var body: some View {
return VStack {
if self.isTerminated {
EmptyView()
} else {
VStack {
CountdownView(duration: duration, onEnded: timerTerminated, start: $timerStart)
Button(action: { self.addWord() }) {
Text("Add words to the deck")
}
}
}
}
}
private func nextWord() {
round.remainingDeck.append("New Word")
}
private func timerTerminated() {
terminateRound()
}
private func terminateRound() {
self.isTerminated = true
}
}
and here is my TimerView code:
struct CountdownView: View {
#State private var remainingTime: Int = 60
#Binding var countingDown: Bool
var onEnded: (() -> Void)?
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
init(duration: Int, onEnded: (() -> Void)?, start: Binding<Bool>) {
self.onEnded = onEnded
self._countingDown = start
self.remainingTime = duration
}
var body: some View {
Text("Remaining \(remainingTime) secs")
.onReceive(timer) {_ in
if self.countingDown {
if self.remainingTime > 0 {
self.remainingTime -= 1
} else {
self.onTerminated()
}
}
}
}
func onTerminated() {
timer.upstream.connect().cancel()
self.remainingTime = 0
onEnded?()
}
}
I have found away to search and return data of current USB's plugged into the system.
I can print this by using print(usbDelegate()) Return:
device added: Cruzer Fit
device added: USB-C Digital AV Multiport Adapter
device added: USB2.0 Hub
device added: USB3.0 Hub
What I would like to do is return these values into an array? Like this:
var usbDevices = [Cruzer Fit, USB-C Digital AV Multiport Adapter, USB2.0 Hub, USB3.0 Hub]
Here is the code that I used to scan for USB's:
import Cocoa
import IOKit
import IOKit.usb
import IOKit.usb.IOUSBLib
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func usbSelect(_ sender: Any) {
print(usbDelegate())
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
public protocol USBWatcherDelegate: class {
/// Called on the main thread when a device is connected.
func deviceAdded(_ device: io_object_t)
/// Called on the main thread when a device is disconnected.
func deviceRemoved(_ device: io_object_t)
}
/// An object which observes USB devices added and removed from the system.
/// Abstracts away most of the ugliness of IOKit APIs.
public class USBWatcher {
private weak var delegate: USBWatcherDelegate?
private let notificationPort = IONotificationPortCreate(kIOMasterPortDefault)
private var addedIterator: io_iterator_t = 0
private var removedIterator: io_iterator_t = 0
public init(delegate: USBWatcherDelegate) {
self.delegate = delegate
func handleNotification(instance: UnsafeMutableRawPointer?, _ iterator: io_iterator_t) {
let watcher = Unmanaged<USBWatcher>.fromOpaque(instance!).takeUnretainedValue()
let handler: ((io_iterator_t) -> Void)?
switch iterator {
case watcher.addedIterator: handler = watcher.delegate?.deviceAdded
case watcher.removedIterator: handler = watcher.delegate?.deviceRemoved
default: assertionFailure("received unexpected IOIterator"); return
}
while case let device = IOIteratorNext(iterator), device != IO_OBJECT_NULL {
handler?(device)
IOObjectRelease(device)
}
}
let query = IOServiceMatching(kIOUSBDeviceClassName)
let opaqueSelf = Unmanaged.passUnretained(self).toOpaque()
// Watch for connected devices.
IOServiceAddMatchingNotification(
notificationPort, kIOMatchedNotification, query,
handleNotification, opaqueSelf, &addedIterator)
handleNotification(instance: opaqueSelf, addedIterator)
// Watch for disconnected devices.
IOServiceAddMatchingNotification(
notificationPort, kIOTerminatedNotification, query,
handleNotification, opaqueSelf, &removedIterator)
handleNotification(instance: opaqueSelf, removedIterator)
// Add the notification to the main run loop to receive future updates.
CFRunLoopAddSource(
CFRunLoopGetMain(),
IONotificationPortGetRunLoopSource(notificationPort).takeUnretainedValue(),
.commonModes)
}
deinit {
IOObjectRelease(addedIterator)
IOObjectRelease(removedIterator)
IONotificationPortDestroy(notificationPort)
}
}
extension io_object_t {
/// - Returns: The device's name.
func name() -> String? {
let buf = UnsafeMutablePointer<io_name_t>.allocate(capacity: 1)
defer { buf.deallocate() }
return buf.withMemoryRebound(to: CChar.self, capacity: MemoryLayout<io_name_t>.size) {
if IORegistryEntryGetName(self, $0) == KERN_SUCCESS {
return String(cString: $0)
}
return nil
}
}
}
class usbDelegate: USBWatcherDelegate {
private var usbWatcher: USBWatcher!
init() {
usbWatcher = USBWatcher(delegate: self)
}
func deviceAdded(_ device: io_object_t) {
print("device added: \(device.name() ?? "<unknown>")")
}
func deviceRemoved(_ device: io_object_t) {
print("device removed: \(device.name() ?? "<unknown>")")
}
}
var example = usbDelegate()
I have been looking for a while now and haven't seen anything to achieve this. Help would greatly be appreciated!
You can return Array from this function
func deviceAdded(_ device: io_object_t)-> [String] {
print("device added: \(device.name() ?? "<unknown>")")
return [device.name()]
}
I still have the problem when the count reaches 3, the reset function only stops it, but the count is not set to 0. I use the reset function with a button, it works perfectly. i would like to understand it and hope someone knows the reason for it?
import SwiftUI
import Combine
import Foundation
class WaitingTimerClass: ObservableObject {
#Published var waitingTimerCount: Int = 0
var waitingTimer = Timer()
func start() {
self.waitingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
self.waitingTimerCount += 1 }}
func stop() { waitingTimer.invalidate() }
func reset() { waitingTimerCount = 0; waitingTimer.invalidate() }
}
struct ContentView: View {
#ObservedObject var observed = WaitingTimerClass()
var body: some View {
VStack {
Text("\(self.observed.waitingTimerCount)")
.onAppear { self.observed.start() }
.onReceive(observed.$waitingTimerCount) { count in
guard count == 3 else {return}
self.observed.reset() // does not work
}
Button(action: {self.observed.start()}) {
Text("Start") }
Button(action: {self.observed.reset()}) { // works
Text("Reset") }
Button(action: {self.observed.stop()}) {
Text("Stop") }
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
It is because reset changes property affecting UI during body construction, so ignored. It should be changed as below
func reset() {
waitingTimer.invalidate()
DispatchQueue.main.async {
self.waitingTimerCount = 0
}
}
I want to connect my url and port from my vector list. I use setTimeout in my reconnect function but my problem is when i use settimeout and click reconnect button settimout is ask 5 times for connection and when connecttion succes after connection is down because settimeout is continue send the request my list for connect and states is broken.
How i solve this problem or have any different way ?
public static function reconnect(serverUrl:String = "", serverPort:int = 0):void {
var nextUri: SocketConnection = urisToTry.next;
test = setTimeout(reconnect, 4000, nextUri.host, nextUri.port);
}
and this is my iteration class
public class UriIterator
{
public var _availableAddresses: Vector.<SocketConnection> = new Vector.<SocketConnection>();
public var currentIndex:int = 0;
public function UriIterator(){
}
public function withAddress(host: String, port: int): UriIterator {
const a: SocketConnection = new SocketConnection(host, port);
_availableAddresses.push(a);
return this;
}
public function get next():SocketConnection {
var address = _availableAddresses[currentIndex];
currentIndex++;
if (currentIndex > _availableAddresses.length - 1)
currentIndex = 0;
return address;
}
}
my list
const urisToTry: UriIterator = new UriIterator()
.withAddress("http://urlone.com", 1211)
.withAddress("http://urltwo.com", 1212)
.withAddress("http://urlthree.com", 1213)
.withAddress("http://urlfour.com", 1214)
...
First, simplify and clean what you have there.
public class UriIterator
{
// You won't gain literally anything from using strong-typed Vector.
// Long variable names make your code harder to read. Use simple ones.
// Another good idea is to make these members private
// so no one else will mess with them from the outside.
private var list:Aray = new Array;
private var index:int = 0;
// If constructor does nothing you might as well omit it.
// It is not prohibited to do it the way you did,
// yet again no gain for unnecessary complications.
public function append(host:String, port:int):void
{
list.push(new SocketConnection(host, port));
}
public function get next():SocketConnection
{
var result:SocketConnection = list[index];
if (++index >= list.length) index = 0;
return result;
}
}
Then.
private var uriList:UriIterator;
uriList = new UriIterator;
uriList.append("http://urlone.com", 1211);
uriList.append("http://urltwo.com", 1212);
uriList.append("http://urlthree.com", 1213);
uriList.append("http://urlfour.com", 1214);
Finally.
private var recId:uint;
// Event handler for refused connection or disconnected socket.
private function onDisconnect(e:Event):void
{
// Dispose of existing/failed connection objects first.
// #TODO
// Just in case.
clearTimeout(recId);
// Now, lets schedule the next connection in 4 seconds.
recId = setTimeout(onReconnect, 4000);
}
private function onReconnect():void
{
// Get the next connection.
var aNext:SocketConnection = uriList.next;
// Put your connection routines here.
// #TODO
}
Here's what I want. I'm writing a very simple event dispatcher (click that link to see my code). It was working fine when I only had the listen() and the fire() method. This is how you could use it:
struct UserHasBirthday: Event {
let name: String
init(name: String) {
self.name = name
}
}
let events = TestDispatcher()
events.listen {
(event: UserHasBirthday) in
print("Happy birthday \(event.name)!")
}
events.fire( UserHasBirthday(name: "John Doe") )
That's all well and good, but now I wanted to add the feature that you could push events to a queue and then later fire them all at once. That's why I added the push and flush methods.
Now the problem is that in the flush() method I need to be able to downcast the generic Event type to the specific event type that was given. Otherwise the fire() method doesn't work.
So I thought, maybe I could save the type information in the same array as the event itself. As you can see I tried to do that with a tuple. Unfortunately it doesn't work like that.
I think that if I could find a way to make the variable pushedEvents accept a generic type like so: var pushedEvents = Array<E: Event>() then it could work. But the only way I know to do that is to assign that generic to the whole class like so: class TestDispatcher<E: Event> { }, but then every instance of that class can only be used for one specific type of event and I definitely don't want that.
Does anybody know some kind of way to make this work?
This guy on reddit gave me the solution by using a so-called type-erasure pattern (I didn't know about that pattern).
I edited his code to meet my needs more and this is what I have now:
public protocol Event {}
public protocol ErasedListener {
func matches(eventType: Event.Type) -> Bool
func dispatchIfMatches(event: Event)
}
public struct Listener<T: Event>: ErasedListener {
let dispatch: T -> Void
public func matches(eventType: Event.Type) -> Bool {
return matches(String(eventType))
}
func matches(eventType: String) -> Bool {
return eventType == String(T.self)
}
public func dispatchIfMatches(event: Event) {
if matches(String(event.dynamicType)) {
dispatch(event as! T)
}
}
}
public protocol Dispatcher {
func listen<E: Event>(listener: E -> Void)
func fire(event: Event)
func queue<E: Event>(event: E)
func flushQueueOf<E: Event>(eventType: E.Type)
func flushQueue()
func forgetListenersFor<E: Event>(event: E.Type)
func emptyQueueOf<E: Event>(eventType: E.Type)
func emptyQueue()
}
public class MyDispatcher: Dispatcher {
var listeners = [ErasedListener]()
var queuedEvents = [Event]()
public init() {}
public func listen<E: Event>(listener: E -> Void) {
let concreteListener = Listener(dispatch: listener)
listeners.append(concreteListener as ErasedListener)
}
public func fire(event: Event) {
for listener in listeners {
listener.dispatchIfMatches(event)
}
}
public func queue<E: Event>(event: E) {
queuedEvents.append(event)
}
public func flushQueue() {
for event in queuedEvents {
fire(event)
}
emptyQueue()
}
public func emptyQueue() {
queuedEvents = []
}
public func flushQueueOf<E: Event>(eventType: E.Type) {
for event in queuedEvents where String(event.dynamicType) == String(eventType) {
fire(event)
}
emptyQueueOf(eventType)
}
public func forgetListenersFor<E: Event>(eventType: E.Type) {
listeners = listeners.filter { !$0.matches(eventType) }
}
public func emptyQueueOf<E: Event>(eventType: E.Type) {
queuedEvents = queuedEvents.filter { String($0.dynamicType) != String(eventType) }
}
}
Example usage
struct UserDied: Event {
var name: String
}
class UserWasBorn: Event {
let year: Int
init(year: Int) {
self.year = year
}
}
// you can use both classes and structs as events as you can see
let daveDied = UserDied(name: "Dave")
let bartWasBorn = UserWasBorn(year: 2000)
var events = MyDispatcher()
events.listen {
(event: UserDied) in
print(event.name)
}
events.listen {
(event: UserWasBorn) in
print(event.year)
}
events.queue(daveDied)
events.queue(UserWasBorn(year: 1990))
events.queue(UserWasBorn(year: 2013))
events.queue(UserDied(name: "Evert"))
// nothing is fired yet, do whatever you need to do first
events.flushQueue()
/*
This prints:
Dave
1990
2013
Evert
*/
// You could also have flushed just one type of event, like so:
events.flushQueueOf(UserDied)
// This would've printed Dave and Evert,
// but not the year-numbers of the other events
The problem is that Swift doesn't allow type conversion to metatypes.
One workaround is to include all types that conform to Event (at least those you will use in your Dispatcher) in a switch case in the flush() function of your TestDispatcher class. It isn't as versatile as the functionality I believe you're looking for, and as you've shown with your own answer, type erasure is the way to here. I'll leave my original answer intact however, as it explains why your original approach of attempting to cast to metatypes didn't work.
public protocol Event {}
public enum Listener<E: Event> {
public typealias T = E -> ()
}
public protocol Dispatcher {
func listen<E: Event>(listener: Listener<E>.T)
func fire<E: Event>(event: E)
func push<E: Event>(event: E)
func flush()
}
//
public class TestDispatcher: Dispatcher {
var listeners = [String:[Any]]()
var pushedEvents = [Event]()
public init() {}
public func listen<E: Event>(listener: Listener<E>.T) {
var listeners = self.listeners[String(E.self)] ?? []
listeners += [listener] as [Any]
self.listeners[String(E.self)] = listeners
}
public func fire<E: Event>(event: E) {
listeners[String(E.self)]?.forEach {
let f = $0 as! Listener<E>.T
f(event)
}
}
public func push<E: Event>(event: E) {
pushedEvents = pushedEvents + [event]
}
/* Include a switch case over all types conforming to Event ... */
public func flush() {
for event in pushedEvents {
switch event {
case let ev as UserHasBirthday: fire(ev)
case let ev as UserWonTheLottery: fire(ev)
case _: print("Unknown event type.")
}
}
}
}
Example usage:
struct UserHasBirthday: Event {
let name: String
init(name: String) {
self.name = name
}
}
struct UserWonTheLottery: Event {
let name: String
let amount: Int
init(name: String, amount: Int) {
self.name = name
self.amount = amount
}
}
let events = TestDispatcher()
events.listen {
(event: UserHasBirthday) in
print("Happy birthday \(event.name)!")
}
events.listen {
(event: UserWonTheLottery) in
print("Congratulations \(event.name) for winning \(event.amount)!")
}
events.push(UserHasBirthday(name: "John Doe"))
events.push(UserHasBirthday(name: "Jane Doe"))
events.push(UserWonTheLottery(name: "Jane Doe", amount: 42000))
events.flush()
/* Happy birthday John Doe!
Happy birthday Jane Doe!
Congratulations Jane Doe for winning 42000! */