Reporting changes from child ViewController - swift

If one ViewController inherits from another, how do I update stuff in the child ViewController as the variable changes in the parent ViewController?
class ViewControllerOne: UIViewController {
var timer = Timer()
var number: Int = 0
func updateNumber() {
number += 1
}
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateNumber), userInfo: nil, repeats: true)
}
}
class ViewControllerTwo: ViewControllerOne {
// So in this class I want to print to the console when number is 3.
// How do I check for that from this class?
}

Override updateNumber in ViewControllerTwo.
override func updateNumber() {
super.updateNumber()
if number == 3 {
// do something
}
}

Related

Swift Delegates not working for updating timer to receiver

I have created protocol as updateTime protocol to update timer to other view controllers conform to updateTime delegates but the timer not updating to other controllers.
Please refer below code and pin point where I did wrong?
protocol UpdateTime: class {
func updateTIme(count: Int)
}
class ViewController: UIViewController, UpdateTime {
#IBOutlet var timeLabel: UILabel!
let secVC = SecondViewController()
override func viewDidLoad() {
super.viewDidLoad()
secVC.delegate = self
// Do any additional setup after loading the view.
}
// must be internal or public.
func updateTIme(count: Int){
print("firstVC: \(count)")
DispatchQueue.main.async {
self.timeLabel.text = "\(count)"
}
}
}
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) -> Int{
count += amount
print(count)
return count
}
func reset() {
count = 0
}
}
This is seconviewcontroller:
class SecondViewController: UIViewController {
#IBOutlet var label: UILabel!
weak var delegate: UpdateTime?
var counter = Counter()
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
DispatchQueue.main.async {
self.timer.invalidate()
self.timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(self.countDown), userInfo: nil, repeats: true)
}
}
#objc func countDown() -> Int{
// counter increments
counter.increment()
let count = counter.increment(by: 5)
DispatchQueue.main.async {
self.label.text = String(describing: count)
}
self.delegate?.updateTIme(count: count)
return count
}
}
The label in view controller not updating count down.
This looks like a lifecycle issue. Though you are creating an instance of SecondViewController, the timer from the second view controller only gets added to the run loop when that view controller's viewDidLoad method is called. Since you are programmatically creating it in the first view controller, there is nowhere in your example that would cause the second view controller's view to get loaded, so the timer would not be added to the runloop.
A better way of accomplishing this issue is to create the timer on the first view controller directly, or create a secondary object (not a second view controller) that manages the timer and have it push updates to the first view controller

Listener in my ViewController to my Custom Cocoapod Class - Swift

I have built my own Cocoapod which currently fires the updateCounting() func once a second. My end goal is to use a protocol so that I can have a delegate method of sorts in my view controller class that fires everytime updateCounting() fires.
My public Cocoapod file currently looks like:
public class Service: NSObject {
var timer = Timer()
public static let shared = Service()
public func scheduledTimerWithTimeInterval(){
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateCounting), userInfo: nil, repeats: true)
}
#objc func updateCounting(){
NSLog("counting..")
}
}
My VC class currently looks like:
import UIKit
import JacquardToolkit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Service.shared.scheduledTimerWithTimeInterval()
}
}
My original thought is to add a protocal to my Service class like so:
public protocol ServiceDelegate: NSObjectProtocol {
func timerFired()
}
public class Service: NSObject, ServiceDelegate {
var timer = Timer()
public static let shared = Service()
public func scheduledTimerWithTimeInterval(){
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerFired), userInfo: nil, repeats: true)
}
#objc public func timerFired() {
NSLog("timerfired")
}
}
So far I have gotten stuck, but I am essentially trying to make a listener in my VC class so that everytime timerFired() or updateCounting() fires, I can detect that in my VC class and act accordingly. Any help is appreciated :)
You have almost done to implement Delegate pattern, but small corrections you may have to do as like below code:
class ViewController: UIViewController, ServiceDelegate {
override func viewDidLoad() {
super.viewDidLoad()
Service.shared.delegate = self
Service.shared.scheduledTimerWithTimeInterval()
}
func timerFired() {
NSLog("timerfired")
}
}
public protocol ServiceDelegate: NSObjectProtocol {
func timerFired()
}
public class Service: NSObject {
var timer = Timer()
public static let shared = Service()
weak var delegate:ServiceDelegate?
public func scheduledTimerWithTimeInterval(){
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerFired), userInfo: nil, repeats: true)
}
#objc public func timerFired() {
NSLog("timerfired")
delegate?.timerFired()
}
}

Triggering event only on a change of variable output

I have created the following coin toss program which also triggers an audio file with each coin toss every second. I'd like to know how to change this so that the audio only triggers when the result changes from a heads to a tails or vice versa. Not every second as it is now. Any help is greatly appreciated.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var times: Timer!
var timer: Timer!
var coinFlip : Int = 0
var coinResult : String = ""
var audioPlayer : AVAudioPlayer!
let soundArray = ["note1", "note7"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
times = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fiftyFifty), userInfo: nil, repeats: true)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(result), userInfo: nil, repeats: true)
result()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fiftyFifty() -> Int {
coinFlip = Int(arc4random_uniform(2))
return coinFlip
}
func result() -> String {
if coinFlip == 1 {
coinResult = "Heads"
print("Heads")
playSound(soundFileName: soundArray[0])
}
else {
coinResult = "Tails"
print("Tails")
playSound(soundFileName: soundArray[1])
}
}
func playSound(soundFileName : String) {
let soundURL = Bundle.main.url(forResource: soundFileName, withExtension: "wav")
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
}
catch {
print(error)
}
audioPlayer.play()
}
}
I've now changed it to add a new variable output from result(), coinResult. Not sure if this helps but hopefully might.
If I understood well, when the coin flips, you want to play a song, in this case you can simply change your coinFlip declaration to that:
var coinFlip : Int = 0 {
didSet {
result()
}
}
didSet is a property observer that observes and respond to changes in property's value.
didSet is called immediately after the new value is stored.
You can read more about property observers in Apple's documentation.
EDIT: Your code will be like this:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var times: Timer!
var timer: Timer!
// Change this
var coinFlip : Int = 0 {
didSet {
result()
}
}
var audioPlayer : AVAudioPlayer!
let soundArray = ["note1", "note7"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
times = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fiftyFifty), userInfo: nil, repeats: true)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(result), userInfo: nil, repeats: true)
result()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fiftyFifty() -> Int {
coinFlip = Int(arc4random_uniform(2))
return coinFlip
}
func result() {
if coinFlip == 1 {
print("Heads")
playSound(soundFileName: soundArray[0])
}
else {
print("Tails")
playSound(soundFileName: soundArray[1])
}
}
func playSound(soundFileName : String) {
let soundURL = Bundle.main.url(forResource: soundFileName, withExtension: "wav")
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
}
catch {
print(error)
}
audioPlayer.play()
}
}

How using class func to call a method from another class in SWIFT

I would to call from the GameViewController my timer method (written in my GameScene) in order to pause the game with an UIButton initialized in GameViewController class.
I'm trying to use a class func like this :
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
GameScene.startTimer()
}
class func startTimer(){
timerCount = NSTimer.scheduledTimerWithTimeInterval(1.0
, target: self, selector: Selector("updateTimer:"), userInfo: nil, repeats: true)
}
func updateTimer(dt:NSTimer){
counter--
counterGame++
if counter<0{
timerCount.invalidate()
removeCountDownTimerView()
} else{
labelCounter.text = "\(counter)"
}
if counterGame>20{
balloon.size = CGSizeMake(50, 50)
}
if counterGame>40{
self.physicsWorld.gravity = CGVectorMake(0, -0.8)
}
if counterGame>60{
self.physicsWorld.gravity = CGVectorMake(0, -1)
}
}
func removeCountDownTimerView(){
defaults.setInteger(balloonDestroyed, forKey: "score")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let settingController: UIViewController = storyboard.instantiateViewControllerWithIdentifier("GameOverViewController") as UIViewController
let vc = self.view?.window?.rootViewController
vc?.presentViewController(settingController, animated: true, completion: nil)
}
}
But this code return an error :
[Funfair_balloon.GameScene updateTimer:]: unrecognized selector sent to class 0x10b13d940
When I don't use the class func the app works perfectly but I can't stop the timer with my UIButton.
What did I do wrong?
Please note that when using class func, you cannot use the variables that were initialized in that class.
So for example, you cannot use the variable timerCount if it was initialized in the class GameScene.
I'm not sure why you want to use class functions. The following should work using just the current instance of GameScene. Note that the var timerCount is an optional (since you can't easily override the init) until such times as you create it in startTimer(), and so you will have to unwrap it when you eventually invalidate it.
class GameScene: SKScene, SKPhysicsContactDelegate {
var timerCount: NSTimer? = nil
var counter = 100 // or whatever
override func didMoveToView(view: SKView) {
self.startTimer()
}
func startTimer() {
self.timerCount = NSTimer.scheduledTimerWithTimeInterval(1.0
, target: self, selector: Selector("updateTimer:"), userInfo: nil, repeats: true)
}
func updateTimer(dt: NSTimer) {
// Do stuff
counter--
if counter < 0 {
self.timerCount!.invalidate() // or dt.invalidate()
self.removeCountDownTimerView()
} else {
// update counter label
}
// Do more stuff
}
func removeCountDownTimerView() {
// Do stuff
}
}

How to pass callback functions in Swift

I have a simple class which init method takes an Int and a callback function.
class Timer {
var timer = NSTimer()
var handler: (Int) -> Void
init(duration: Int, handler: (Int) -> Void) {
self.duration = duration
self.handler = handler
self.start()
}
#objc func someMethod() {
self.handler(10)
}
}
Then in the ViewController I have this:
var timer = Timer(duration: 5, handler: displayTimeRemaining)
func displayTimeRemaining(counter: Int) -> Void {
println(counter)
}
This doesn't work, I get the following:
'Int' is not a subtype of 'SecondViewController'
Edit 1: Adding full code.
Timer.swift
import UIKit
class Timer {
lazy var timer = NSTimer()
var handler: (Int) -> Void
let duration: Int
var elapsedTime: Int = 0
init(duration: Int, handler: (Int) -> Void) {
self.duration = duration
self.handler = handler
self.start()
}
func start() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
target: self,
selector: Selector("tick"),
userInfo: nil,
repeats: true)
}
func stop() {
timer.invalidate()
}
func tick() {
self.elapsedTime++
self.handler(10)
if self.elapsedTime == self.duration {
self.stop()
}
}
deinit {
self.timer.invalidate()
}
}
SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
#IBOutlet var cycleCounter: UILabel!
var number = 0
var timer = Timer(duration: 5, handler: displayTimeRemaining)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnIncrementCycle_Click(sender: UIButton){
cycleCounter.text = String(++number)
println(number)
}
func displayTimeRemaining(counter: Int) -> Void {
println(counter)
}
}
I just started with Swift so I'm very green. How are you supposed to pass callbacks? I've looked at examples and this should be working I think. Is my class defined incorrectly for the way I'm passing the callback?
Thanks
Ok, now with the full code I was able to replicate your issue. I'm not 100% sure what the cause is but I believe it has something to do with referencing a class method (displayTimeRemaining) before the class was instantiated. Here are a couple of ways around this:
Option 1: Declare the handler method outside of the SecondViewController class:
func displayTimeRemaining(counter: Int) -> Void {
println(counter)
}
class SecondViewController: UIViewController {
// ...
var timer = Timer(duration: 5, handler: displayTimeRemaining)
Option 2: Make displayTimeRemaining into a type method by adding the class keyword to function declaration.
class SecondViewController: UIViewController {
var timer: Timer = Timer(duration: 5, handler: SecondViewController.displayTimeRemaining)
class func displayTimeRemaining(counter: Int) -> Void {
println(counter)
}
Option 3: I believe this will be the most inline with Swift's way of thinking - use a closure:
class SecondViewController: UIViewController {
var timer: Timer = Timer(duration: 5) {
println($0) //using Swift's anonymous method params
}
Simplest way
override func viewDidLoad() {
super.viewDidLoad()
testFunc(index: 2, callback: { str in
print(str)
})
}
func testFunc(index index: Int, callback: (String) -> Void) {
callback("The number is \(index)")
}
Your problem is in the line:
var timer = NSTimer()
You cannot instantiate an NSTimer in this way. It has class methods that generate an object for you. The easiest way to get around the problem in this case is to make the definition lazy, like so:
lazy var timer = NSTimer()
In this way the value for timer won't actually be touched until the start method which sets it up properly. NOTE: There is probably a safer way to do this.