Using reduce to change struct in Swift - ios

I'm trying to understand how to change a struct (or class?) in an array by using reduce. Creating 4 countdown timers, on tap pause the current timer and start the next. So I tried something like this:
var timer1 = CountdownTimer()
// var timer2 = CountdownTimer() etc.
.onTapGesture(perform: {
var timers: [CountdownTimer] = [timer1, timer2, timer3, timer4]
var last = timers.reduce (false) {
(setActive: Bool, nextValue: CountdownTimer) -> Bool in
if (nextValue.isActive) {
nextValue.isActive = false;
return true
} else {
return false
}
}
if (last) {
var timer = timers[0]
timer.isActive = true
}
})
############# CountdownTimer is a struct ######
struct CountdownTimer {
var timeRemaining: CGFloat = 1000
var isActive: Bool = false
}
This does not work, two errors I'm seeing
the timers in the array are copies of the timers, not the actual timer so changing them doesn't actually change the timers being displayed on screen.
nextValue (i.e. the next timer) can't be changed because it's a let variable in the reduce declaration. I don't know how to change this (or if it's even relevant because presumably it's a copy of the copy of the timer and not the one I actually want to change).
Am I approaching this in a way thats idiomatically wrong for Swift? How should I be changing the original timers?

I agree with Paul about the fact that this should likely all be pulled out into an observable model object. I'd make that model hold an arbitrary list of timers, and the index of the currently active timer. (I'll give an example of that at the end.)
But it's still worth exploring how you would make this work.
First, SwiftUI Views are not the actual view on the screen like in UIKit. They are descriptions of the view on the screen. They're data. They can be copied and destroyed at any time. So they're readonly objects. The way you keep track of their writable state is through #State properties (or similar things like #Binding, #StateObject, #ObservedObject and the like). So your properties need to be marked #State.
#State var timer1 = CountdownTimer()
#State var timer2 = CountdownTimer()
#State var timer3 = CountdownTimer()
#State var timer4 = CountdownTimer()
As you've discovered, this kind of code doesn't work:
var timer = timer1
timer.isActive = true
That makes a copy of timer1 and modifies the copy. Instead, you want WriteableKeyPath to access the property itself. For example:
let timer = \Self.timer1 // Note capital Self
self[keyPath: timer].isActive = true
Finally, reduce is the wrong tool for this. The point of reduce is to reduce a sequence to a single value. It should never have side-effects like modifying the values. Instead, you just want to find the right elements, and then change them.
To do that, it would be nice to be able to easily track "this element and the next one, and the last element is followed by the first." That seems very complicated, but it's surprisingly simple if you include Swift Algorithms. That gives cycled(), which returns a Sequence that repeats its input forever. Why is that useful? Because then you can do this:
zip(timers, timers.cycled().dropFirst())
This returns
(value1, value2)
(value2, value3)
(value3, value4)
(value4, value1)
Perfect. With that I can fetch the first active timer (keypath) and its successor, and update them:
let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]
if let (current, next) = zip(timers, timers.cycled().dropFirst())
.first(where: { self[keyPath: $0.0].isActive })
{
self[keyPath: current].isActive = false
self[keyPath: next].isActive = true
}
That said, I wouldn't do that. There are subtle requirements here that should be captured in a type. In particular, you have this assumption that there is only one active timer, but nothing enforces that. If that's what you mean, you should make a type that says so. For example:
class TimerBank: ObservableObject {
#Published private(set) var timers: [CGFloat] = []
#Published private(set) var active: Int?
var count: Int { timers.count }
init(timers: [CGFloat]) {
self.timers = timers
self.active = timers.startIndex
}
func addTimer(timeRemaining: CGFloat = 1000) {
timers.append(timeRemaining)
}
func start(index: Int? = nil) {
if let index = index {
active = index
} else {
active = timers.startIndex
}
}
func stop() {
active = nil
}
func cycle() {
if let currentActive = active {
active = (currentActive + 1) % timers.count
print("active = \(active)")
} else {
active = timers.startIndex
print("(init) active = \(active)")
}
}
}
With this, timerBank.cycle() replaces your reduce.

By using the modulus operator ( % ) on Index, we could cycle through last to first without zipping.
let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]
if let onIndex = timers.firstIndex(where: { self[keyPath: $0].isActive }) {
self[keyPath: timers[onIndex]].isActive = false
let nextIndex = (onIndex + 1) % 4 // instead of 4, could use timers.count
self[keyPath: timers[nextIndex]].isActive = true
}

Related

How to observe a change in a class's property from another class

I've got a question on property observers. There's some example code below. What I want is for the property Analysis.hasChanged to be updated to true if a.value is changed. Is there a way I can do this?
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
You can use the built-in property observers provided by Swift.
Every time you set a new value, the didSet will be called. You just need to attach the closure, wrapping the desired behaviour, to the Number class
class Number {
var valueDidChangeClosure: (()->())?
var value: Double {
didSet {
//won't call the valueDidChangeClosure
//if the value was changed from 10 to 10 for example..
if oldValue != value {
valueDidChangeClosure?()
}
}
}
init(numberValue: Double) {
self.value = numberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
self.a.valueDidChangeClosure = {
self.hasChanged = true
}
}
}
let testNumber = Number(numberValue: 4)
let testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will print "true"
I would do something like this, I apologize in advance if I have some syntax wrong (I usually use C/C++, think of this as more psudo code since you'd have to have a way to copy Number classes, etc.).
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var _a: Number
bool hasChanged() {
if (a != _a) {
_a = a
return true;
}
return false;
}
init(inputNumber: Number) {
self.a = inputNumber
self._a = self.a
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged()) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged()) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
I don't know if this really addresses that question, I based my answer off of the code you provided. So there may be additional functionality if you want there to be some triggering method (instead of calling .hasChanged()).
Comparing doubles (and any other floating point type) with '=' or '!=' is not a good idea.
Use epsilon function instead.
Details: jessesquires.com/blog/floating-point-swift-ulp-and-epsilon/

Running! from VCVCVC

I have been trying for the last 18 months taking all my code out of the ViewController to make it more like MVC should. ( Hence the title )
I've made a small step on my own by being able to do the following within one Class object, but now I want to break it down further
A very generic example: one file contains the data
struct Data {
var x = 0
var y = 0
}
and one file for the operation
class Adder {
var myObject = MyClass()
var z = 1
func addThem() {
z = myObject.x + myObject.y
}
}
now for the salient parts of the ViewController:
var data = Data()
var adder = Adder()
#IBAction func buttonPressed(sender: UIButton) {
// user input via textfield
data.x = Int(numeralOne.text!)!
data.y = Int(numeralTwo.text!)!
adder.addThem() // *
answerLabel.text = String(adder.z)
}
Ultimately I'd like to omit the line commented with the asterisk. I thought OO's encapsulation of (data) away from (adder) allows for adder.z to just automatically update in the background without involving the ViewController. That way a subtractor class (say) can operate on the same two struct properties.
My question? How can correctly referencing them from the VC.
PS. if i include return statements in the function it makes no difference.
It's not clear from your code exactly what the relationship is between the Data struct and MyClass but if a MyClass object has the values of x & y that you need then the following will work
class MyClass {
var x = 1
var y = 2
}
class Adder {
var myObject = MyClass()
var z: Int {
return myObject.x + myObject.y
}
}
let adder = Adder()
print(adder.z)

Why is this dispatch_after firing instantly?

Here's a struct I've written to convert an NSTimeInterval into a walltime-based dispatch_time_t:
public struct WallTimeKeeper {
public static func walltimeFrom(spec: timespec)->dispatch_time_t {
var mutableSpec = spec
let wallTime = dispatch_walltime(&mutableSpec, 0)
return wallTime
}
public static func timeStructFrom(interval: NSTimeInterval)->timespec {
let nowWholeSecsFloor = floor(interval)
let nowNanosOnly = interval - nowWholeSecsFloor
let nowNanosFloor = floor(nowNanosOnly * Double(NSEC_PER_SEC))
println("walltimekeeper: DEBUG: nowNanosFloor: \(nowNanosFloor)")
var thisStruct = timespec(tv_sec: Int(nowWholeSecsFloor),
tv_nsec: Int(nowNanosFloor))
return thisStruct
}
}
I've been trying to test the accuracy of it in a Playground, but my results are confusing me.
Here's the code in my Playground (with my WallTimeKeeper in the Sources folder):
var stop = false
var callbackInterval: NSTimeInterval?
var intendedTime: NSDate?
var intendedAction: ()->() = {}
func testDispatchingIn(thisManySeconds: NSTimeInterval){
intendedTime = NSDate(timeIntervalSinceNow: thisManySeconds)
intendedAction = stopAndGetDate
dispatchActionAtDate()
loopUntilAfterIntendedTime()
let success = trueIfActionFiredPunctually() //always returns false
}
func dispatchActionAtDate(){
let timeToAct = dateAsDispatch(intendedTime!)
let now = dateAsDispatch(NSDate())
/*****************
NOTE: if you run this code in a Playground, comparing the above two
values will show that WallTimeKeeper is returning times the
correct number of seconds apart.
******************/
dispatch_after(timeToAct, dispatch_get_main_queue(), intendedAction)
}
func loopUntilAfterIntendedTime() {
let afterIntendedTime = intendedTime!.dateByAddingTimeInterval(1)
while stop == false && intendedTime?.timeIntervalSinceNow > 0 {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode,
beforeDate: afterIntendedTime)
}
}
func trueIfActionFiredPunctually()->Bool{
let intendedInterval = intendedTime?.timeIntervalSinceReferenceDate
let difference = intendedInterval! - callbackInterval!
let trueIfHappenedWithinOneSecondOfIntendedTime = abs(difference) < 1
return trueIfHappenedWithinOneSecondOfIntendedTime
}
func dateAsDispatch(date: NSDate)->dispatch_time_t{
let intendedAsInterval = date.timeIntervalSinceReferenceDate
let intendedAsStruct = WallTimeKeeper.timeStructFrom(intendedAsInterval)
let intendedAsDispatch = WallTimeKeeper.walltimeFrom(intendedAsStruct)
return intendedAsDispatch
}
func stopAndGetDate() {
callbackInterval = NSDate().timeIntervalSinceReferenceDate
stop = true
}
testDispatchingIn(3)
...so not only doestrueIfActionFiredPunctually() always returns false, but the difference value--intended to measure the difference between the time the callback fired and the time it was supposed to fire--which in a successful result should be really close to 0, and certainly under 1--instead comes out to be almost exactly the same as the amount of time the callback was supposed to wait to fire.
In summary: an amount of time to wait is defined, and an action is set to fire after that amount of time. When the action fires, it creates a timestamp of the moment it fired. When the timestamp is compared to the value it should be, instead of getting close to zero, we get close to the amount of time we were supposed to wait.
In other words, it appears as if the action passed to dispatch_after is firing immediately, which it absolutely shouldn't!
Is this something wrong with Playgrounds or wrong with my code?
EDIT:
It's the code. Running the same code inside a live app gives the same result. What am I doing wrong?
I figured it out. It's a head-smacker. I'll leave it up in case anyone is having the same problem.
I was using NSDate().timeIntervalSinceReferenceDate to set my walltimes.
Walltimes require NSDate().timeIntervalSince1970!
The dispatch_after tasks all fired instantly because they thought they were scheduled for over forty years ago!
Changing everything to NSDate().timeIntervalSince1970 makes it work perfectly.
Moral: don't use walltimes unless you're sure your reference date is 1970!

The count in my For loop is not incrementing

When running my code, I am getting a number of 1's printing to the console rather than 1,2,3,4,5....
Some help with why this is happening would be great, I'm having trouble figuring it out.
The idea is to loop through the Calendar names until finding the 'Travel' calendar.
func checkCalendarExists(){
var eventCalendars = store.calendarsForEntityType(EKEntityTypeEvent) as [EKCalendar]
for i in eventCalendars {
var count = 0
var calendarCount = eventCalendars.count
if i.title != "Travel" && count != calendarCount
{
++count
println(count)
}
else if i.title == "Travel"
{
// do something
}
else
{
aMethod()
}
}
}
Your count variable is not being incremented because it is declared inside the loop and initialized to the value zero at the beginning of each iteration. For your code to work as expected you have to move var count = 0 outside the for loop.
Your count variable does get incremented, but it resets to zero every time the for loop runs its sequence.
It's always advised to declare and assign incrementing variables outside loops.
Please change your code to (I am initializing var count = 0 before the loop)
func checkCalendarExists(){
var eventCalendars = store.calendarsForEntityType(EKEntityTypeEvent) as [EKCalendar]
var count = 0
for i in eventCalendars {
var calendarCount = eventCalendars.count
......
......
......
else
{
aMethod()
}
}
}
ALXGTV's answer explains why you have that unexpected behavior.
Your code can be optimized though - rather than manually handling a counter variable, I recommend using the enumerate function, which returns a (index, value) at each iteration:
for (index, calendar) in enumerate(eventCalendars) {
...
}
Also this variable:
var calendarCount = eventCalendars.count
is populated at each iteration, always with the same value. It would be more efficient if it is moved before the loop, making it immutable:
let calendarCount = eventCalendars.count
for (index, calendar) in enumerate(eventCalendars) {
...
}
Last, I would prefer using a flag for the not found condition, handling it outside the loop:
func checkCalendarExists() {
var eventCalendars = store.calendarsForEntityType(EKEntityTypeEvent) as [EKCalendar]
var found = false
let calendarCount = eventCalendars.count
for (index, calendar) in enumerate(eventCalendars) {
if calendar.title == "Travel" {
// do something
found = true
break // This stops the loop
} else {
println(index + 1)
}
}
if !found {
aMethod()
}
}

How to create a random range for enemies to appear?

I'm making a game where hero should jump over the enemies using Sprite Kit and Swift. I have 4 types of them but they're basically the same. When I coded appearance of enemies I thought it would be ok if they could appear in group but now I see that it's not very nice. How can I make a random range between enemies to appear? It should provide enough space for hero to land from a jump at least. I tried but end up with something bizarre.
That's how I'm spawning them:
func random() -> UInt32 {
var time = UInt32(60)..<UInt32(200)
return time.startIndex + arc4random_uniform(time.endIndex - time.startIndex + 1)
}
Then I have different file for appearance:
class EnemyAppear {
var nowAppear = false
var waitToAppear = UInt32(0)
var appearInterval = UInt32(0)
init(nowAppear:Bool, waitToAppear:UInt32, appearInterval:UInt32) {
self.nowAppear = nowAppear
self.waitToAppear = waitToAppear
self.appearInterval = appearInterval
}
func shouldRun() -> Bool {
return self.appearInterval > self.waitToAppear
}
}
then I have a function to run them on screen
func enemyRun() {
for(enemy, enemyAppear) in self.enemyStatus {
var thisPet = self.childNodeWithName(enemy)!
if enemyAppear.shouldRun() {
enemyAppear.waitToAppear = random()
enemyAppear.appearInterval = 0
enemyAppear.nowAppear = true
}
And then for each enemy I have a "Status"
var enemyStatus:Dictionary<String,EnemyAppear> = [:]
enemyStatus["mouse"] = EnemyAppear(nowAppear: false, waitToAppear: random(), appearInterval: UInt32(0))
I can't understand how to make them appear just like that but with an empty space between each other and where to put it.

Resources