How do I make a label keep switching between two colors in Swift? - swift

I am trying to make my label flash two colors on repeat. I want it to continuously repeat the text color Blue and Black. Is this possible? And can someone help me do this?

You can do it by using NSTimer and below is complete code for that:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var lbl: UILabel!
var timer : NSTimer?
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}
var textColorIsBlue = false
func update() {
if !textColorIsBlue {
lbl.textColor = UIColor.blueColor()
} else {
lbl.textColor = UIColor.blackColor()
}
textColorIsBlue = !textColorIsBlue
}
}
And your result will be:
And you can modify timer as per your need.
And whenever you don't need time you can stop it by using timer?.invalidate().

I couldn't help but create an alternative example
class ViewController: UIViewController {
#IBOutlet private var label: UILabel! {
didSet {
label.text = "This is just some sample text"
}
}
private var timer: dispatch_source_t?
private var generator: AlternatingColorGenerator?
deinit {
guard let timer = timer else { return }
dispatch_source_cancel(timer)
}
override func viewDidLoad() {
super.viewDidLoad()
generator = AlternatingColorGenerator(colors: [.redColor(), .blueColor(), .greenColor()])
let t = repeatingTimer(1, queue: dispatch_get_main_queue()) { [weak self] () -> Void in
self?.label.textColor = self?.generator?.next()
}
dispatch_resume(t)
timer = t
label.textColor = generator?.next()
}
}
private class AlternatingColorGenerator: GeneratorType {
var currentIndex: Int = 0
let numberOfColors: Int
let colors: [UIColor]
init(colors: [UIColor]) {
self.colors = colors
numberOfColors = colors.count
}
func next() -> UIColor? {
let color = colors[currentIndex]
currentIndex = (currentIndex + 1) % numberOfColors
return color
}
}
private func repeatingTimer(interval: NSTimeInterval, queue: dispatch_queue_t, action: () -> Void) -> dispatch_source_t {
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
let timerInterval = UInt64(interval) * NSEC_PER_SEC
let timerLeeway = timerInterval / 10
dispatch_source_set_event_handler(timer, action)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, timerInterval, timerLeeway)
return timer
}
This uses a GCD timer, rather than an NSTimer, and rather than toggle the colours with a method, I've created a generator.
This uses a different programming style: one where the generation of things (timers and colours to use) is separated from the place where they are consumed.

Related

reduce the cell background based on time swift

I would like to make sure that my cell has a background related to the remaining time. in the sense that the closer I get to 0, the more I would like it to be reduced, so that we understand that the timer is about to expire.
according to the elapsed time it automatically reduces from right to left.
this is the code I use in managing the Cell
class TimerCell: UITableViewCell {
#IBInspectable var defaultBackgroundColor: UIColor = .white
#IBInspectable var runningBackgroundColor: UIColor = .white
#IBInspectable var pausedBackgroundColor: UIColor = .white
#IBInspectable var animationDuration: Double = 0
#IBOutlet var timeLabel: UILabel!
#IBOutlet var nameLabel: UILabel!
#IBOutlet var startButton: UIButton!
#IBOutlet var pauseButton: UIButton!
#IBOutlet var stopButton: UIButton!
weak var timer: Timer? {
didSet {
guard let timer = timer else {
updater?.invalidate()
return
}
if case .running = timer.state {
startUpdater()
}
configure(animated: false)
}
}
private weak var updater: Foundation.Timer?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setEditing(_ editing: Bool, animated: Bool) {
print("*** \(Date()) setEditing(\(editing), animated: \(animated)) (timer?.name: \(String(describing: timer?.name)))")
super.setEditing(editing, animated: animated)
configure(animated: animated)
}
func configure(animated: Bool = true) {
guard let timer = timer else {
return
}
UIView.animate(withDuration: animated ? animationDuration : 0) {
guard !self.isEditing else {
self.timeLabel.text = timer.initialTime.hmsString
self.startButton.safelySetIsHidden(true)
self.pauseButton.safelySetIsHidden(true)
self.stopButton.safelySetIsHidden(true)
self.backgroundColor = self.defaultBackgroundColor
return
}
self.timeLabel.text = ceil(timer.timeForState).hmsString
self.nameLabel.text = timer.name
switch timer.state {
case .stopped:
self.stopButton.safelySetIsHidden(true)
self.pauseButton.safelySetIsHidden(true)
self.startButton.safelySetIsHidden(false)
self.backgroundColor = self.defaultBackgroundColor
case .running:
self.startButton.safelySetIsHidden(true)
self.stopButton.safelySetIsHidden( ceil(timer.timeForState) == 0 ? true : false )
self.pauseButton.safelySetIsHidden( ceil(timer.timeForState) == 0 ? true : false )
self.backgroundColor = self.runningBackgroundColor
case .paused:
self.pauseButton.safelySetIsHidden(true)
self.startButton.safelySetIsHidden(false)
self.stopButton.safelySetIsHidden(false)
self.backgroundColor = self.pausedBackgroundColor
}
}
}
#IBAction private func startTimer() {
timer?.state = .running
configure()
startUpdater()
}
#IBAction private func pauseTimer() {
timer?.state = .paused
configure()
}
#IBAction private func stopTimer() {
timer?.state = .stopped
configure()
}
private func startUpdater() {
guard let timer = timer else {
return
}
let date = Date(timeIntervalSinceNow: timer.timeForState.truncatingRemainder(dividingBy: 1))
let updater = Foundation.Timer(fire: date, interval: 1, repeats: true) {
[weak timer] updater in
self.configure()
if timer?.state != .running {
updater.invalidate()
}
}
self.updater = updater
RunLoop.main.add(updater, forMode: .common)
}
}
I think you're after something like this:
That's not trivial to achieve. I did it by adding a CAGradientLayer to the view and animating its locations property. At the same time, I ran the timer to change the label value.
So you might do it that way; you would probably want to tweak the values, of course. This is just a proof-of-concept demo.

How can I monitor currentPosition of AVMIDIPlayer, please?

I'm trying to make a simple music player. I want the label to display the current position of the AVMIDIPlayer. With my code the label is only updated once at the very beginning:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player:AVMIDIPlayer = AVMIDIPlayer()
var playerTime:Double = 999 {
didSet {
label.text = String(playerTime)
}
}
#IBOutlet var label: UILabel!
#IBAction func Play(_ sender: Any) {
player.play()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
do {
let audioPath = Bundle.main.path(forResource: “song”, ofType: "mid")
try player = AVMIDIPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL, soundBankURL: nil)
playerTime = player.currentPosition
}
catch {
}
}
}
What have I missed, please? Thank you. Scarlett
The reason the label isn’t updating is you’re setting the text in viewDidLoad which is called only once. Use a Timer to update the label.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player:AVMIDIPlayer = AVMIDIPlayer()
var playerTime:Double = 999 {
didSet {
label.text = String(playerTime)
}
}
var timer = Timer()
#IBOutlet var label: UILabel!
#IBAction func Play(_ sender: Any) {
player.play()
// this will execute every 0.1 seconds, allowing you to update the label.
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
self.playerTime += 0.1
let min = self.playerTime.truncatingRemainder(dividingBy: 3600)) / 60
let sec = self.playerTime.truncatingRemainder(dividingBy: 60)
self.label.text = String(format: "%02d:%02d", min, sec)
}
}
func stop() {
// when you stop playback
timer.invalidate()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
do {
let audioPath = Bundle.main.path(forResource: “song”, ofType: "mid")
try player = AVMIDIPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL, soundBankURL: nil)
playerTime = player.currentPosition
}
catch {
}
}
}

How to detect user inactivity in OS X writing in Swift Cocoa?

I have searched answers in stackoverflow and none of them matches my needs. I am creating time tracking app on Swift Cocoa macOS, like Hubstaff time tracking app. At the moment runs a timer and I want to detect user's inactivity after x period of time and to send a Notification that he has been Idle x period of time. I'm new to iOS and macOS development. Can I have an example of how to do it?
Here is my code:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var label: NSTextField!
#IBOutlet weak var playImage: NSButton!
var timer : Timer!
var isTimerWorking : Bool = false
var startTime : Date!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func playPause(_ sender: NSButton) {
if isTimerWorking {
endTimer()
playImage.image = NSImage(named: NSImage.Name("play"))
sender.state = .off
} else {
startTimer()
playImage.image = NSImage(named: NSImage.Name("stop"))
sender.state = .off
}
}
func startTimer() {
startTime = Date()
timer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(self.timerCounter),
userInfo: nil,
repeats: true
)
isTimerWorking = true
}
func endTimer() {
if timer != nil {
timer.invalidate()
label.stringValue = "00:00:00"
}
isTimerWorking = false
}
#objc func timerCounter() {
let currentTime = Date().timeIntervalSince(startTime)
let hour = Int(fmod(currentTime/3600, 60))
let minute = Int(fmod(currentTime/60, 60))
let second = Int(fmod(currentTime, 60))
let hourValue = String(format:"%02d", hour)
let minuteValue = String(format:"%02d", minute)
let secondValue = String(format:"%02d", second)
label.stringValue = "\(hourValue):\(minuteValue):\(secondValue)"
}
}
In my own time tracking app I am using
var lastEvent:CFTimeInterval = 0
lastEvent = CGEventSource.secondsSinceLastEventType(CGEventSourceStateID.hidSystemState, eventType: CGEventType(rawValue: ~0)!)
print(lastEvent)
to get the user idle time.

AVPlayer loads multiple times before playing - long delay before streaming

I'm facing a very unique situation, AVPlayer is trying to stream a URL that it loads multiple times (I see in control centre) before eventually playing it, after a long delay of about 120 seconds or so (sometimes more). I don't know why that is happening. I use a singleton instance of KDEAudioPlayer - any help would be appreciated.
KDEAudioPlayer Code:
import UIKit
import KDEAudioPlayer
var urlToPlay : NSURL!
var titleTrackBeingPlayed = String?()
class MediaPlayerViewController: FXBlurView, AudioPlayerDelegate {
//MARK: Variables
var seekToTime = NSTimeInterval()
var streaming = true
var timer:NSTimer?
var change:CGFloat = 0.01
//MARK: Connections
#IBOutlet weak var loadingActivityIndicator: UIActivityIndicatorView!
#IBOutlet weak var streamingFromServerLabel: UILabel!
#IBOutlet weak var timelineSlider: UISlider!
#IBOutlet weak var totalTimeLabel: UILabel!
#IBOutlet weak var nowTimeLabel: UILabel!
#IBOutlet weak var toggleButton: UIButton!
#IBOutlet weak var volumeControlSlider: UISlider!
#IBOutlet weak var titleLabel: UILabel!
//MARK: Actions
#IBAction func endTrackPlayerButtonPressed(sender: UIButton) {
//TODO: R&D how to dismiss.
print("media player view to be dismissed")
if let viewWithTag = self.viewWithTag(1089) {
print("Tag 1089")
Singleton.stop()
viewWithTag.removeFromSuperview()
}
}
#IBAction func timelineSliderValueChanged(sender: UISlider) {
Singleton.pause()
Singleton.seekToTime(NSTimeInterval(sender.value))
timelineSlider?.setValue(sender.value, animated: true)
Singleton.resume()
}
#IBAction func playPauseButtonPressed(sender: UIButton) {
toggle()
}
//MARK: Initializer for Class
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.tintColor = UIColor.clearColor()
if Singleton.isPlaying {
Singleton.isPlaying = false
}
playerSetup()
toggle()
}
//MARK: Setup for Audio Player
func playerSetup() {
Singleton.playItemWithUrl(urlToPlay)
Singleton.player.delegate = self
timelineSlider?.continuous = true
self.titleLabel?.text = titleTrackBeingPlayed
}
internal func refreshAudioView(_ :NSTimer) {
}
func toggle(){
if Singleton.isPlaying {
pauseTrack()
toggleButton.setImage(UIImage(named: "Play"), forState: .Normal)
}
else if !Singleton.isPlaying {
playTrack()
toggleButton?.setImage(UIImage(named: "Pause"), forState: .Normal)
}
}
func audioPlayer(audioPlayer: AudioPlayer, willStartPlayingItem item: AudioItem) {
titleLabel.text = titleTrackBeingPlayed
}
func audioPlayer(audioPlayer: AudioPlayer, didFindDuration duration: NSTimeInterval, forItem item: AudioItem) {
totalTimeLabel?.text = makeTimeString(Float(duration))
timelineSlider?.maximumValue = Float(duration)
timelineSlider?.minimumValue = 0.0
self.titleLabel?.text = titleTrackBeingPlayed
}
func audioPlayer(audioPlayer: AudioPlayer, didUpdateProgressionToTime time: NSTimeInterval, percentageRead: Float) {
timelineSlider?.setValue(Float(time), animated: true)
nowTimeLabel?.text = makeTimeString(Float(time))
Singleton.player.volume = volumeControlSlider.value
seekToTime = time
}
override func remoteControlReceivedWithEvent(event: UIEvent?) {
if let event = event {
Singleton.player.remoteControlReceivedWithEvent(event)
self.canBecomeFirstResponder()
}
}
func audioPlayer(audioPlayer: AudioPlayer, didChangeStateFrom from: AudioPlayerState, toState to: AudioPlayerState) {
if to == .Playing {
if streaming {
streamingFromServerLabel.hidden = false
loadingActivityIndicator.hidden = true
toggleButton?.setImage(UIImage(named: "Pause"), forState: .Normal)
}
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: #selector(TrackMediaPlayerViewController.refreshAudioView(_:)), userInfo: nil, repeats: true)
}
else if to == .Buffering || to == .WaitingForConnection {
loadingActivityIndicator.hidden = false
}
else {
streamingFromServerLabel.hidden = true
}
}
func playTrack() {
if seekToTime > 0.0 {
Singleton.resume()
} else {
Singleton.play()
}
Singleton.isPlaying = true
}
func pauseTrack() {
Singleton.pause()
Singleton.isPlaying = false
}
//MARK: Convert NSTime to String
func makeTimeString(value:Float) -> String
{
if value == 0 || value.isNaN
{
return "00:00"
}
let hr = (Int)(value / 3600)
let min = (Int)((value % 3600) / 60)
let sec = (Int)((value % 3600) % 60)
let timestamp = String(format:"%d:%02d.%02d", hr, min, sec)
return timestamp
}
}
Singleton Code:
import Foundation
import UIKit
import KDEAudioPlayer
class Singleton: AudioPlayerDelegate {
static let shareInstance = DarsPlayerSingleton()
static let player = AudioPlayer()
static var isPlaying = Bool()
private init (){
}
static func playItemWithUrl(url: NSURL) {
let item = AudioItem(mediumQualitySoundURL: url)
player.playItem(item!)
}
static func play() {
player.resume()
isPlaying = true
}
static func pause() {
player.pause()
isPlaying = false
}
static func stop() {
player.stop()
}
static func seekToTime(time: NSTimeInterval){
player.seekToTime(time)
}
static func resume() {
player.resume()
}
}

Running a Timer that counts down in background when app isnt the focus? Swift

I want my countdown timer to suspend and then resume when the app leaves / returns to focus, using the time away to calculate how much time should be deducted.
I am using the app delegate file (not sure thats the right location? or if they are meant to be in the view controllers file as functions of their own?)
Issue is im getting a lot of errors such as:
Value of type 'AppDelegate' has no member 'restTimer'
Use of unresolved identifier 'nextFireDate'
Use of unresolved identifier 'selector'
restTimer was declared as a timer in my view controllers file but when i tried these blocks in that file i got an equal number of errors for unresolved identifiers
and using the following 2 code blocks
func applicationWillResignActive(_ application: UIApplication) {
guard let t = self.restTimer else { return }
nextFireDate = t.fireDate
t.invalidate()
and
func applicationDidBecomeActive(_ application: UIApplication) {
guard let n = nextFireDate else { return }
let howMuchLonger = n.timeIntervalSinceDate(NSDate())
if howMuchLonger < 0 {
print("Should have already fired \(howMuchLonger) seconds ago")
target!.performSelector(selector!)
} else {
print("should fire in \(howMuchLonger) seconds")
Timer.scheduledTimerWithTimeInterval(howMuchLonger, target: target!, selector: selector!, userInfo: nil, repeats: false)
}
}
UPDATE: Added full views code due to issue incorporating the answer
import Foundation
import UIKit
class RestController: UIViewController {
#IBOutlet weak var restRemainingCountdownLabel: UILabel!
#IBOutlet weak var setsRemainingCountdownLabel: UILabel!
#IBOutlet weak var numberOfSetsLabel: UILabel!
#IBOutlet weak var numberOfRestLabel: UILabel!
#IBOutlet weak var adjustSetsStepper: UIStepper!
#IBOutlet weak var adjustRestStepper: UIStepper!
var startDate: Date!
let startDateKey = "start.date"
let interval = TimeInterval(20)
var restTimer: Timer!
var restCount = 0
var setCount = 0
var selectedTime = 1
var selectedSets = 1
private let resignDateKey = "resign.date"
#IBAction func endSetPressed(_ sender: Any) {
if (setCount > 0){
setCount -= 1
setsRemainingCountdownLabel.text = String(setCount)
}
handleTimer()
}
#IBAction func setStepperValueChanged(_ sender: UIStepper) {
numberOfSetsLabel.text = Int(sender.value).description
self.setCount = Int(sender.value)
self.selectedSets = setCount
setsRemainingCountdownLabel.text = String(setCount)
}
#IBAction func restStepperValueChanged(_ sender: UIStepper) {
numberOfRestLabel.text = Int(sender.value).description
let timeMinSec = timeFormatted(totalSeconds: Int(sender.value)*60)
restRemainingCountdownLabel.text = timeMinSec
self.selectedTime = Int(sender.value)
restCount = self.selectedTime * 60
}
#IBAction func resetSetsButton(_ sender: Any) {
setCount = Int(adjustSetsStepper.value)
setsRemainingCountdownLabel.text = String(setCount)
}
override func viewDidLoad() {
super.viewDidLoad()
numberOfSetsLabel.text = String(selectedSets)
numberOfRestLabel.text = String(selectedTime)
createTimer(interval: interval)
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc private func willResignActive(notification: Notification) {
print("resigning")
guard restTimer.isValid else {
UserDefaults.standard.removeObject(forKey: startDateKey)
return
}
restTimer.invalidate()
UserDefaults.standard.set(Date(), forKey: startDateKey)
}
#objc private func didBecomeActive(notification: Notification) {
print("resume")
if let startDate = UserDefaults.standard.object(forKey: startDateKey) as? Date {
let elapsed = -startDate.timeIntervalSinceNow
print("elpased time: \(elapsed) remaining time: \(interval - elapsed)")
if elapsed > interval {
timerUp()
} else {
createTimer(interval: interval - elapsed)
}
}
}
private func createTimer (interval: TimeInterval) {
restTimer = Timer.scheduledTimer(withTimeInterval: interval , repeats: false) {[weak self] _ in
self?.timerUp()
}
startDate = Date()
}
private func timerUp() {
print("At least \(interval) seconds has elapsed")
}
func handleSets() {
if (setCount > 0) {
self.restCount = self.selectedTime * 60
}
handleTimer()
}
func handleTimer() {
if (restTimer?.isValid ?? false) {
restTimer?.invalidate()
restTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(RestController.updateTimer), userInfo: nil, repeats: true)
} else {
restTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(RestController.updateTimer), userInfo: nil, repeats: true)
}
}
func updateTimer() {
if (restCount > 0){
restCount -= 1
} else if (restCount == 0){
restTimer?.invalidate()
}
restRemainingCountdownLabel.text = timeFormatted(totalSeconds: restCount)
}
func timeFormatted(totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
let minutes: Int = (totalSeconds / 60) % 60
return String(format: "%02d:%02d", minutes, seconds)
}
I think you cannot rely on the fact that the app will remain in memory while it is in the background. Thus, you should archive all the data you need to recreate the timer at the exact point.
For example, in applicationWillResignActive
UserDefaults.standard.set(value: t.nextFireDate forKey:"NextFireDate")
and in applicationWillEnterForeground
if let fireDate = UserDefaults.standard.object(forKey: "NextFireDate") {
// setup a timer with the correct fire date
}
You don't have to use the AppDelegate for this because it also posts notifications. You can use the AppDelegate if you want. Here is code using notifications:
class ViewController: UIViewController{
private let startDateKey = "start.date"
private let interval = TimeInterval(20)
private var startDate: Date!
private var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
createTimer(interval: interval)
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc private func willResignActive(notification: Notification) {
print("resigning")
guard timer.isValid else {
UserDefaults.standard.removeObject(forKey: startDateKey)
return
}
timer.invalidate()
UserDefaults.standard.set(Date(), forKey: startDateKey)
}
#objc private func didBecomeActive(notification: Notification) {
print("resume")
if let startDate = UserDefaults.standard.object(forKey: startDateKey) as? Date {
let elapsed = -startDate.timeIntervalSinceNow
print("elpased time: \(elapsed) remaining time: \(interval - elapsed)")
if elapsed > interval {
timerUp()
} else {
createTimer(interval: interval - elapsed)
}
}
}
private func createTimer (interval: TimeInterval) {
timer = Timer.scheduledTimer(withTimeInterval: interval , repeats: false) {[weak self] _ in
self?.timerUp()
}
startDate = Date()
}
private func timerUp() {
print("At least \(interval) seconds has elapsed")
}
}