Executing morse code with flashlight from array - arrays

I have an array of characters "." and "-" which is a translated text to morse code. They are of course in a extacly specified order because of the morse code. Now i want to convert this array to long and short flashes from flashlight in iPhone, after button is tapped. I tried of course looping through this array with 'if' statement, with scheduledTimers just like below, but obviously that doesnt worked out, loop was executing immediately and flashlight was launching only once.
let dash: Character = "-"
let dot: Character = "."
for i in translatedArray {
if i == dash{
//two sec delay at the beggining because of the pause beetween each flash which is equal to one dot
timer1 = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(toggleFlashlightOn), userInfo: nil, repeats: false)
timer2 = Timer.scheduledTimer(timeInterval: 6, target: self, selector: #selector(toggleFlashlightOff), userInfo: nil, repeats: false)
print("long flash activated")
}else if i == dot{
timer1 = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(toggleFlashlightOn), userInfo: nil, repeats: false)
timer2 = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(toggleFlashlightOff), userInfo: nil, repeats: false)
print("short flash activated")
} else { return }
}
I also tried to delay separate 'if' iteration with delay() function, but execution of this loop with morse code should have irregular time interval beetween each iteration depending on previous character aka previous lenght of flash, and delay() have only static value of seconds, so this didn't worked either.
Finally i found this thread here. And except updating it to swift4, and changing array of morse code characters to array of time intervals, which i did succesfully this would be a perfect solution for my problem... but it doesn't work. It turns on flashlight in only one mode which is ON, and refresh endlesly printing "ON" regularly with first time interval value in the array, for example every 2 seconds or so. So it never turns off the flashlight.
let shortFlash: Double = 2
let longFlash: Double = 4
let pause: Double = 2
var sequenceOfFlashes: [Double] = []
//this works ok, prints correct values in correct order from sequenceOfFlashes array
func setupMorseFlashesSequence(){
let dot: Character = "•"
let line: Character = "—"
for i in translatedArray {
switch i {
case dot:
sequenceOfFlashes.append(shortFlash)
sequenceOfFlashes.append(pause)
case line:
sequenceOfFlashes.append(longFlash)
sequenceOfFlashes.append(pause)
default:
return
}
}
print(sequenceOfFlashes)
}
var index: Int = 0
weak var timer: Timer?
func scheduleTimer(){
timer = Timer.scheduledTimer(timeInterval: sequenceOfFlashes[index], target: self, selector: #selector(timerTick), userInfo: nil, repeats: false)
}
#objc func timerTick(){
if index == sequenceOfFlashes.count {
stop()
}
turnFlashlight(on: index % 2 == 0)
scheduleTimer()
}
func start(){
index = 0
turnFlashlight(on: true)
scheduleTimer()
}
func stop(){
timer?.invalidate()
turnFlashlight(on: false)
}
deinit{
timer?.invalidate()
}
how can i achieve executing this flashes one by one with different times?

You forgot to increment index in timerTick():
#objc func timerTick(){
index += 1
if index == sequenceOfFlashes.count {
stop()
}
turnFlashlight(on: index % 2 == 0)
scheduleTimer()
}

Related

Select middle value in array - Swift

I'm trying to make sure that the middle value Lists is the first view that is seen when building my application. Xcode offers if let firstView = viewList.first and if let firstView = viewList.last but I can't workout how to select the middle value.
lazy var viewList:[UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let lists = sb.instantiateViewController(withIdentifier: "Lists")
let reminders = sb.instantiateViewController(withIdentifier: "Reminders")
let todo = sb.instantiateViewController(withIdentifier: "To Do")
return [reminders, lists, todo]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
if let firstView = viewList.first {
self.setViewControllers([firstView], direction: .forward, animated: true, completion: nil)
}
}
Similar to first and last, you can extend Array with a computed middle property that returns an optional Element.
extension Array {
var middle: Element? {
guard count != 0 else { return nil }
let middleIndex = (count > 1 ? count - 1 : count) / 2
return self[middleIndex]
}
}
Usage Example:
if let middleView = viewList.middle {
//... Do something
}
I want you to be aware that first and last can return the same element if the array has only 1 element.
Similarly, though this extension will work for any array length, it can return the same element for:
first, middle & last if your array has only 1 element
middle & last if your array has only 2 elements
Can add an extension to Array to accomplish this:
extension Array {
var middleIndex: Int {
return (self.isEmpty ? self.startIndex : self.count - 1) / 2
}
}
let myArray: [String] = ["Hello", "World", "!"]
print("myArray.middleIndex: \(myArray.middleIndex)") // prints 1
Since viewList declared as [UIViewController] (not as optionals - [UIViewController?]), you don't have to "optional binding" (checking the whether the element is nil or not) because it has to be exist. what you should do instead is to check if the index in the range (making sure that the index is in the range).
Logically speaking (obviously), if you are pretty sure that viewList always has 3 elements, there is no need to do any check, just:
let middleViewController = viewList[1]
In case of the number of elements in viewList is undetermined and you are aiming to get the middle element, you simply get it as:
let middleViewController = viewList[(viewList.count - 1) / 2]
Remember, first and last are optionals, in your case there is no need to work with optionals...
Your viewList is an array of type UIViewController. And first and last only denotes their 0th and last index. like:
viewList.first is same as viewList[0]
viewList.last is same as viewList[viewList.count - 1]
Only difference in using these are if you will use viewList.first it will return nil if your array is empty, but if you will use viewList[0] on empty array, you app will be crash with error index out of bound...
So, you can easily access your middle value with index as:
if viewList.count > 1 {
let middleView = viewList[1]
self.setViewControllers([middleView], direction: .forward, animated: true, completion: nil)
}
If you are not sure about the viewList.count will be 3 or more, Then:
let middleIndex = (viewList.count - 1) / 2
let middleView = viewList[middleIndex]

Multiple timers in a loop

I have an array of characters, eg.
["A","E","I","O","U","Y"]
I should loop this array and, for every character, start a timer. That is: if the current letter is A, A must be printed and last 2 seconds. After A has been printed, we need to print "E" and it should last 3 seconds, "I" one second, "O" four seconds, etc.
I think I should create a new timer for every character, this way:
Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector:
#selector(printCharacter), userInfo: nil, repeats: false)
but I can't do it inside the loop.
I hoped there was a way to build an array of timers and then fire them sequentially, but this is not possible. Any idea?
You can achieve this using recursion and DispatchQueue only. If you are not familiar with recursion a simple way to understand it's by googling it
A sample code if print duration isn't linear would look like following :
var currentIndex = 0
let charactersArray = ["A","B","C","D"]
let durations : [TimeInterval] = [1.0,2.0,3.0,4.0]
func printCharacter(){
guard currentIndex < charactersArray.count else { return }
print(charactersArray[currentIndex])
DispatchQueue.main.asyncAfter(deadline: .now() + durations[currentIndex], execute: {
currentIndex = currentIndex + 1
printCharacter()
})
}
printCharacter()
where durations array represent time shown for each character

Timer does not invalidate

I can not invalidate a Swift Timer.
When defining my objects and variables I declare the timer with
var scoreTimer = Timer()
I start the timer when two objects collide with this line:
scoreTimer = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(updatePassengerScore), userInfo: nil, repeats: true)
My updatePassengerScore method looks like this
passengerScore -= 1
scoreLabel.text = String(passengerScore)
When another collision is detected, I try to stop the timer with
scoreTimer.invalidate()
but the variable passengerScore is still decreasing and I do not have a clue why this is the case.
Does anyone have a clue hoe to solve this and make the Timer stop? Every Thread I read says that scoreTimer.invalidate() should actually stop the timer. I am really confused :-/
You might be starting multiple timers. If you hit the line of code that creates a timer before the current one has been invalidated, then you will have two active timers, only one of which you can invalidate.
To prevent this possibility, call invalidate on scoreTimer before creating a new timer:
scoreTimer.invalidate()
scoreTimer = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(updatePassengerScore), userInfo: nil, repeats: true)
The other way to handle it is to change scoreTimer to an optional, and then only create a timer if scoreTimer is nil.
var scoreTimer: Timer?
...
if scoreTimer == nil {
scoreTimer = Timer.scheduledTimer(...
}
...
scoreTimer?.invalidate()
scoreTimer = nil

How to loop through array to find 4 identical values that are consecutive?

I have the following swift array:
var winSuitArray = [cardSuit1, cardSuit2, cardSuit3, cardSuit4, cardSuit5, cardSuit6, cardSuit7]
cardSuit1, cardSuit2 and so on, are variables that will equal strings like "clubs" or "hearts". What I want to do is loop through this array, and if the loop finds 4 identical objects whose indexes are consecutive, set the winSuitStatus bool to true.
For example, if the array looks like this:
["hearts", "clubs", "clubs", "clubs", "clubs", "diamonds", "spades"]
I want to loop through it like so:
for card in winSuitArray {
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
Is this possible to do?
To tell the truth, I'd probably do something similar to #KnightOfDragon's answer. There's nothing wrong with that approach. But this problem opens up some opportunities to build some much more reusable code at little cost, so it seems worth a little trouble to do that.
The basic problem is that you want to create a sliding window of a given size over the list, and then you want to know if any of the windows contain only a single value. So the first issue to to create these windows. We can do that very generally for all collections, and we can do it lazily so we don't have to compute all the windows (we might find our answer at the start of the list).
extension Collection {
func slidingWindow(length: Int) -> AnyRandomAccessCollection<SubSequence> {
guard length <= count else { return AnyRandomAccessCollection([]) }
let windows = sequence(first: (startIndex, index(startIndex, offsetBy: length)),
next: { (start, end) in
let nextStart = self.index(after: start)
let nextEnd = self.index(after: end)
guard nextEnd <= self.endIndex else { return nil }
return (nextStart, nextEnd)
})
return AnyRandomAccessCollection(
windows.lazy.map{ (start, end) in self[start..<end] }
)
}
}
The use of AnyRandomAccessCollection here is to just hide the lazy implementation detail. Otherwise we'd have to return a LazyMapSequence<UnfoldSequence<(Index, Index), ((Index, Index)?, Bool)>, SubSequence>, which would be kind of crazy.
Now are next question is whether all the elements in a window are equal. We can do that for any kind of Equatable sequence:
extension Sequence where Iterator.Element: Equatable {
func allEqual() -> Bool {
var g = makeIterator()
guard let f = g.next() else { return true }
return !contains { $0 != f }
}
}
And with those two pieces, we can just ask our question. In the windows of length 4, are there any runs that area all equal?
let didWin = suits.slidingWindow(length: 4).contains{ $0.allEqual() }
Or we could go a little different way, and create a SlidingWindowSequence that we could iterate over. The logic here is basically the same. This just wraps up the windowing into a specific type rather than a AnyRandomAccessCollection. This may be overkill for this problem, but it demonstrates another powerful pattern.
public struct SlidingWindowSequence<Base: Collection>: Sequence, IteratorProtocol {
let base: Base
let windowSize: Base.IndexDistance
private var windowStart: Base.Index
public init(_ base: Base, windowSize: Base.IndexDistance) {
self.base = base
self.windowSize = windowSize
self.windowStart = base.startIndex
}
public mutating func next() -> Base.SubSequence? {
if base.distance(from: windowStart, to: base.endIndex) < windowSize {
return nil
}
let window = base[windowStart..<base.index(windowStart, offsetBy: windowSize)]
windowStart = base.index(after: windowStart)
return window
}
}
let didWin = SlidingWindowSequence(suits, windowSize: 4).contains{ $0.allEqual() }
var suit = ""
var count = 1
for card in winSuitArray {
if(suit == card)
{
count++
}
else
{
count = 1
suit = card
}
if(count == 4)
{
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
}
You can use a counter variable to do this, initialized to 1.
for each value in array:
if value equals previous value
increment counter
else
counter = 1
if counter >= 4
set winCounter to true

How to fill UISegmentedControl Indexes with String Array Values in Swift

I have a UISegmentedControl with 4 indexes that contains quiz answers that are gathered from a string array. My goal is to fill all 4 indexes with wrong answers, and then randomly replace one of those indexes with the correct answer. When I run the simulator, the app appears to enter an infinite loop and the view never loads. When I comment-out the entire second for-loop (which is supposed to fill the indexes with incorrect guesses), the view loads and the segmented control appears with just the correct answer displayed in one segment. Why is the infinite loop being entered (if that is the case) and how can I modify this code to display the segmented controls as desired?
func nextQuestion()
{
questionNumberLabel.text = String(format: "Question %1$d of %2$d",
(correctGuesses + 1), numberOfQuestions)
answerLabel.text = ""
correctAnswer = allAnimals.removeAtIndex(0)
animalImageView.image = UIImage(named: correctAnswer) // next animal
// re-enable UISegmentedControls and delete prior segments
for segmentedControl in segmentedControls
{
segmentedControl.enabled = true
segmentedControl.removeAllSegments()
}
// place guesses on displayed UISegmentedControls
allAnimals.shuffle() // shuffle array
var i = 0
for segmentedControl in segmentedControls
{
if !segmentedControl.hidden
{
var segmentIndex = 0
while segmentIndex < 4
{
if i < allAnimals.count && correctAnswer != allAnimals[i]
{
segmentedControl.insertSegmentWithTitle(
getStringFromFile(allAnimals[i]),
atIndex: segmentIndex, animated: false)
++segmentIndex
}
++i
}
}
}
// pick random segment and replace with correct answer
let randomIndexInRow = Int(arc4random_uniform(UInt32(4)))
segmentedControls[0].removeSegmentAtIndex(
randomIndexInRow, animated: false)
segmentedControls[0].insertSegmentWithTitle(
getStringFromFile(correctAnswer),
atIndex: randomIndexInRow, animated: false)
}
The solution is:
selectedSegmentIndex = 2

Resources