Letter by letter animation for UILabel? - ios

Is there a way to animate the text displayed by UILabel. I want it to show the text value character by character.
Help me with this folks

Update for 2018, Swift 4.1:
extension UILabel {
func animate(newText: String, characterDelay: TimeInterval) {
DispatchQueue.main.async {
self.text = ""
for (index, character) in newText.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay * Double(index)) {
self.text?.append(character)
}
}
}
}
}
calling it is simple and thread safe:
myLabel.animate(newText: myLabel.text ?? "May the source be with you", characterDelay: 0.3)
#objC, 2012:
Try this prototype function:
- (void)animateLabelShowText:(NSString*)newText characterDelay:(NSTimeInterval)delay
{
[self.myLabel setText:#""];
for (int i=0; i<newText.length; i++)
{
dispatch_async(dispatch_get_main_queue(),
^{
[self.myLabel setText:[NSString stringWithFormat:#"%#%C", self.myLabel.text, [newText characterAtIndex:i]]];
});
[NSThread sleepForTimeInterval:delay];
}
}
and call it in this fashion:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
[self animateLabelShowText:#"Hello Vignesh Kumar!" characterDelay:0.5];
});

Here's #Andrei G.'s answer as a Swift extension:
extension UILabel {
func setTextWithTypeAnimation(typedText: String, characterInterval: NSTimeInterval = 0.25) {
text = ""
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
for character in typedText.characters {
dispatch_async(dispatch_get_main_queue()) {
self.text = self.text! + String(character)
}
NSThread.sleepForTimeInterval(characterInterval)
}
}
}
}

This might be better.
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *string =#"Risa Kasumi & Yuma Asami";
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:string forKey:#"string"];
[dict setObject:#0 forKey:#"currentCount"];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(typingLabel:) userInfo:dict repeats:YES];
[timer fire];
}
-(void)typingLabel:(NSTimer*)theTimer
{
NSString *theString = [theTimer.userInfo objectForKey:#"string"];
int currentCount = [[theTimer.userInfo objectForKey:#"currentCount"] intValue];
currentCount ++;
NSLog(#"%#", [theString substringToIndex:currentCount]);
[theTimer.userInfo setObject:[NSNumber numberWithInt:currentCount] forKey:#"currentCount"];
if (currentCount > theString.length-1) {
[theTimer invalidate];
}
[self.label setText:[theString substringToIndex:currentCount]];
}

I have write a demo , you can use it , it support ios 3.2 and above
in your .m file
- (void)displayLabelText
{
i--;
if(i<0)
{
[timer invalidate];
}
else
{
[label setText:[NSString stringWithFormat:#"%#",[text substringToIndex:(text.length-i-1)]]];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 60)];
[label setBackgroundColor:[UIColor redColor]];
text = #"12345678";
[label setText:text];
[self.view addSubview:label];
i=label.text.length;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(displayLabelText) userInfo:nil repeats:YES];
[timer fire];
}
in your .h file
#interface labeltextTestViewController : UIViewController {
UILabel *label;
NSTimer *timer;
NSInteger i;
NSString *text;
}
with the demo , i think you can do in your situation , with a little change
the code look like very very ugly because i have to go to have dinner, you can majorization it.

Swift 3 ,Still credit on Andrei G. concept.
extension UILabel{
func setTextWithTypeAnimation(typedText: String, characterInterval: TimeInterval = 0.25) {
text = ""
DispatchQueue.global(qos: .userInteractive).async {
for character in typedText.characters {
DispatchQueue.main.async {
self.text = self.text! + String(character)
}
Thread.sleep(forTimeInterval: characterInterval)
}
}
}
}

I have written a lightweight library specifically for this use case called CLTypingLabel, available on GitHub.
It is efficient, safe and does not sleep any thread. It also provide pause and continue interface. Call it anytime you want and it won't break.
After installing CocoaPods, add the following like to your Podfile to use it:
pod 'CLTypingLabel'
Sample Code
Change the class of a label from UILabel to CLTypingLabel;
#IBOutlet weak var myTypeWriterLabel: CLTypingLabel!
At runtime, set text of the label will trigger animation automatically:
myTypeWriterLabel.text = "This is a demo of typing label animation..."
You can customize time interval between each character:
myTypeWriterLabel.charInterval = 0.08 //optional, default is 0.1
You can pause the typing animation at any time:
myTypeWriterLabel.pauseTyping() //this will pause the typing animation
myTypeWriterLabel.continueTyping() //this will continue paused typing animation
Also there is a sample project that comes with cocoapods

Update: 2019, swift 5
It works! Just copy paste my answer & see your result
Also create an #IBOutlet weak var titleLabel: UILabel! before the viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.text = ""
let titleText = "⚡️Please Vote my answer"
var charIndex = 0.0
for letter in titleText {
Timer.scheduledTimer(withTimeInterval: 0.1 * charIndex, repeats: false) { (timer) in
self.titleLabel.text?.append(letter)
}
charIndex += 1
}
}

SwiftUI + Combine example:
struct TypingText: View {
typealias ConnectablePublisher = Publishers.Autoconnect<Timer.TimerPublisher>
private let text: String
private let timer: ConnectablePublisher
private let alignment: Alignment
#State private var visibleChars: Int = 0
var body: some View {
ZStack(alignment: self.alignment) {
Text(self.text).hidden() // fixes the alignment in position
Text(String(self.text.dropLast(text.count - visibleChars))).onReceive(timer) { _ in
if self.visibleChars < self.text.count {
self.visibleChars += 1
}
}
}
}
init(text: String) {
self.init(text: text, typeInterval: 0.05, alignment: .leading)
}
init(text: String, typeInterval: TimeInterval, alignment: Alignment) {
self.text = text
self.alignment = alignment
self.timer = Timer.TimerPublisher(interval: typeInterval, runLoop: .main, mode: .common).autoconnect()
}
}

There is no default behaviour in UILabel to do this, you could make your own where you add each letter one at a time, based on a timer

I know it is too late for the answer but just in case for someone who is looking for the typing animation in UITextView too. I wrote a small library Github for Swift 4. You can set the callback when animation is finished.
#IBOutlet weak var textview:TypingLetterUITextView!
textview.typeText(message, typingSpeedPerChar: 0.1, completeCallback:{
// complete action after finished typing }
Also, I have UILabel extension for typing animation.
label.typeText(message, typingSpeedPerChar: 0.1, didResetContent = true, completeCallback:{
// complete action after finished typing }

I wrote this based on the first answer:
import Foundation
var stopAnimation = false
extension UILabel {
func letterAnimation(newText: NSString?, completion: (finished : Bool) -> Void) {
self.text = ""
if !stopAnimation {
dispatch_async(dispatch_queue_create("backroundQ", nil)) {
if var text = newText {
text = (text as String) + " "
for(var i = 0; i < text.length;i++){
if stopAnimation {
break
}
dispatch_async(dispatch_get_main_queue()) {
let range = NSMakeRange(0,i)
self.text = text.substringWithRange(range)
}
NSThread.sleepForTimeInterval(0.05)
}
completion(finished: true)
}
}
self.text = newText as? String
}
}
}

Based on #Adam Waite answer.
If someone would like to use this with a completion closure.
func setTextWithTypeAnimation(typedText: String, characterInterval: NSTimeInterval = 0.05, completion: () ->()) {
text = ""
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
for character in typedText.characters {
dispatch_async(dispatch_get_main_queue()) {
self.text = self.text! + String(character)
}
NSThread.sleepForTimeInterval(characterInterval)
}
dispatch_async(dispatch_get_main_queue()) {
completion()
}
}
}

Modifying #Adam Waite's code (nice job, btw) for displaying the text word by word:
func setTextWithWordTypeAnimation(typedText: String, characterInterval: NSTimeInterval = 0.25) {
text = ""
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
let wordArray = typedText.componentsSeparatedByString(" ")
for word in wordArray {
dispatch_async(dispatch_get_main_queue()) {
self.text = self.text! + word + " "
}
NSThread.sleepForTimeInterval(characterInterval)
}
}

A little improvement to the answer provided by Andrei, not to block the main thread.
- (void)animateLabelShowText:(NSString*)newText characterDelay:(NSTimeInterval)delay
{
[super setText:#""];
NSTimeInterval appliedDelay = delay;
for (int i=0; i<newText.length; i++)
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, appliedDelay * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[super setText:[NSString stringWithFormat:#"%#%c", self.text, [newText characterAtIndex:i]]];
});
appliedDelay += delay;
}

I wrote a small open source lib to do it. I built it with NSAttributedString such that the label won't resize during the animation. It also supports typing sounds and curser animation
Check out here:
https://github.com/ansonyao/AYTypeWriter

Related

iOS (Swift): Changing UILabel text with a for loop

I have a for loop as follows:
#objc private func resetValue() {
for i in stride(from: value, to: origValue, by: (value > origValue) ? -1 : 1) {
value = i
}
value = origValue
}
And when value is set it updates a label:
private var value = 1 {
didSet {
updateLabelText()
}
}
private func updateLabelText() {
guard let text = label.text else { return }
if let oldValue = Int(text) { // is of type int?
let options: UIViewAnimationOptions = (value > oldValue) ? .transitionFlipFromTop : .transitionFlipFromBottom
UIView.transition(with: label, duration: 0.5, options: options, animations: { self.label.text = "\(value)" }, completion: nil)
} else {
label.text = "\(value)"
}
}
I was hoping that if value=5 and origValue=2, then the label would flip through the numbers 5,4,3,2. However, this is not happening - any suggestions why, please?
I've tried using a delay function:
func delay(_ delay:Double, closure: #escaping ()->()) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
and then placing the following within the stride code:
delay(2.0) { self.value = i }
However, this doesn't seem to work either.
Thanks for any help offered.
UIKit won't be able to update the label until your code is finished with the main thread, after the loop completes. Even if UIKit could update the label after each iteration of the loop, the loop is going to complete in a fraction of a second.
The result is that you only see the final value.
When you attempted to introduce the delay, you dispatched the update to the label asynchronously after 0.5 second; Because it is asynchronous, the loop doesn't wait for the 0.5 second before it continues with the next iteration. This means that all of the delayed updates will execute after 0.5 seconds but immediately one after the other, not 0.5 seconds apart. Again, the result is you only see the final value as the other values are set too briefly to be visible.
You can achieve what you want using a Timer:
func count(fromValue: Int, toValue: Int) {
let stride = fromValue > toValue ? -1 : 1
self.value = fromValue
let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats:true) { [weak self] (timer) in
guard let strongSelf = self else {
return
}
strongSelf.value += stride
if strongSelf.value == toValue {
timer.invalidate()
}
}
}
I would also update the didSet to send the oldValue to your updateLabelText rather than having to try and parse the current text.
private var value = 1 {
didSet {
updateLabelText(oldValue: oldValue, value: value)
}
}
private func updateLabelText(oldValue: Int, value: Int) {
guard oldValue != value else {
self.label.text = "\(value)"
return
}
let options: UIViewAnimationOptions = (value > oldValue) ? .transitionFlipFromTop : .transitionFlipFromBottom
UIView.transition(with: label, duration: 0.5, options: options, animations: { self.label.text = "\(value)" }, completion: nil)
}

How to load counter form current text in Objective C

I am new in iOS and I am facing problem to load counter from where it left count.
My code is like this
In
viewDidLoad()
{
if (!_currentTimeInSeconds) {
}_currentTimeInSeconds = 0 ;
if (!_myTimer) {
_myTimer = [self createTimer];
}
}
- (NSTimer *)createTimer {
return [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timerTicked:)
userInfo:nil
repeats:YES];
}
- (NSString *)formattedTime:(int)totalSeconds
{
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
return [NSString stringWithFormat:#"%02d:%02d:%02d",hours, minutes, seconds];
}
- (void)timerTicked:(NSTimer *)timer {
_currentTimeInSeconds++;
clockLabel.text = [self formattedTime:_currentTimeInSeconds];
}
-(void)updateTime
{
NSDate *date= [NSDate date];
NSDateFormatter *formatter1 = [[NSDateFormatter alloc]init]; //for hour and minute
formatter1.dateFormat = #"hh:mm:ss";// use any format
clockLabel.text = [formatter1 stringFromDate:date];
}
For Save
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
Timmer=clockLabel.text;
[defaults setValue:Timmer forKey:#"Timmer"];
[defaults synchronize];
For Retrive
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
Timmer=[defaults objectForKey:#"Timmer"];
clockLabel.text=Timmer;
This code is setting value from 0.
I need to set the value from where I left count.How to do this?
Thanks in Advance!
If you want to retrieve the count even after you quit and relaunch the app, then you should save/store the value into NSUserDefaults.
Just Set value in NSUserDefaults when you leave your controller . And this whole code tested and its work completely fine. see above commented video.
var _currentTimeInSeconds : Int = Int()
var _myTimer : Timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
_currentTimeInSeconds = 0 ;
_myTimer = self.createTimer()
}
func createTimer()-> Timer {
return Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerTicked), userInfo: nil, repeats: true)
}
func timerTicked(_ timer: Timer) {
_currentTimeInSeconds += 1;
label.text = self.formattedTime(self._currentTimeInSeconds)
}
func formattedTime(_ totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
let minutes: Int = (totalSeconds / 60) % 60
let hours: Int = totalSeconds / 3600
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
func updateTime() {
let date = Date()
let formatter1 = DateFormatter()
//for hour and minute
formatter1.dateFormat = "hh:mm:ss"
// use any format
label.text = formatter1.string(from: date)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
print(_currentTimeInSeconds)
let defaults = UserDefaults.standard
defaults.setValue(_currentTimeInSeconds, forKey: "Timmer")
defaults.synchronize()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let defaults = UserDefaults.standard
if (defaults.object(forKey: "Timmer") != nil){
_currentTimeInSeconds = defaults.object(forKey: "Timmer") as! Int
print(_currentTimeInSeconds)
}
}
Happy Coding.
- (void)viewDidLoad {
timeSec=[[NSString stringWithFormat:#"%ld",(long)[[NSUserDefaults standardUserDefaults]integerForKey:#"timeSec"]] intValue];
timeMin=[[NSString stringWithFormat:#"%ld",(long)[[NSUserDefaults standardUserDefaults]integerForKey:#"timeMin"]] intValue];
[self StartTimer];
}
-(void) StartTimer
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
self.lbl_timer.text=#"00:00";
}
- (void)timerTick:(NSTimer *)timer
{
timeSec++;
if (timeSec == 60)
{
timeSec = 0;
timeMin++;
}
self.lbl_timer.text= [NSString stringWithFormat:#"%02d:%02d", timeMin, timeSec];;
}
when you back to the screen that time add this code.like back button or close button action
[timer invalidate];
[[NSUserDefaults standardUserDefaults]setInteger:timeSec forKey:#"timeSec"];
[[NSUserDefaults standardUserDefaults]setInteger:timeMin forKey:#"timeMin"];

How to access for loop externally and make it stop in swift

I'm using this function to make the text write letter by letter:
extension SKLabelNode {
func setTextWithTypeAnimation(typedText: String, characterInterval: NSTimeInterval = 0.05) {
text = ""
self.fontName = "PressStart2P"
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
for character in typedText.characters {
dispatch_async(dispatch_get_main_queue()) {
self.text = self.text! + String(character)
}
NSThread.sleepForTimeInterval(characterInterval)
}
}
}
And, if the user clicks the screen, I want to make the for loop stop and show the complete text instantly.
I would do something like this:
var ignoreSleeper = false
#IBAction func pressButton(sender: UIButton) {
ignoreSleeper = true
}
extension SKLabelNode {
func setTextWithTypeAnimation(typedText: String, characterInterval: NSTimeInterval = 0.05) {
text = ""
self.fontName = "PressStart2P"
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
for character in typedText.characters {
dispatch_async(dispatch_get_main_queue()) {
self.text = self.text! + String(character)
}
if(!ignoreSleeper){
NSThread.sleepForTimeInterval(characterInterval)
}
}
}
}
Edit: like #Breek already mentioned
I'd suggest to implement a tiny NSTimer with a counter for the number of chars to display. Start the Timer with a repeat count of typedText.characters.count and the desired delay and you're good to go (with one thread). Increment the number of chars counter on each timer loop. You can stop this timer at any time with a button press by calling invalidate on the timer.
Example
var timer: NSTimer?
var numberOfCharsToPrint = 1
let text = "Hello, this is a test."
func updateLabel() {
if numberOfCharsToPrint == text.characters.count {
welcomeLabel.text = text
timer?.invalidate()
}
let index = text.startIndex.advancedBy(numberOfCharsToPrint)
welcomeLabel.text = text.substringToIndex(index)
numberOfCharsToPrint++;
}
Then initialize your timer whenever you want the animation to start.
timer = NSTimer.scheduledTimerWithTimeInterval(0.25, target: self, selector: "updateLabel", userInfo: nil, repeats: true)
You can invalidate/stop the timer at any given time with timer?.invalidate().

Can I animate scale marker in GoogleMaps SDK for iOS?

Refer to Do marker animations exist on GoogleMaps SDK for iOS? and Google Dev Guide I understand that I can only animate marker when it appear using appearAnimation and animate opacity via GMSMarkerLayer
Since GMSMarkerLayer.icon is UIImage. Can I animate Scale the GMSMarker icon like an app called Find My Friends? (When tap the marker, it animate scale or zoom the marker icon bigger)
Thank you.
You can scale the gmsMarker icon by changing the ui image assigned to it.
Ideally you should have the callback to the animation function from an ui thread (in the snippet is done via another thread) to prevent flickers.
Here's a sample code snippet to test the concept:
BOOL runInflateAnimation=YES;
float curScaleFactor=1.0f;
float curScaleDirection=-1.0f;
float scaleStep=0.2f;
float scaleMin=0.5f;
float scaleMax=2.0f;
double lastTimeStamp;
-(void) startBouncyAnimation
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,(unsigned long)NULL), ^(void)
{
while (runBouncyAnimation)
{
double curTimeStamp =[[NSDate date] timeIntervalSince1970];
double delta;
if (lastTimeStamp>0)
{
delta = curTimeStamp - lastTimeStamp;
curScaleFactor+=scaleStep*curScaleDirection * delta;
if (curScaleFactor<scaleMin)
{
curScaleFactor=scaleMin;
curScaleDirection*=-1.0f;
} else if (curScaleFactor>scaleMax)
{
curScaleFactor=scaleMax;
curScaleDirection*=-1.0f;
}
[self hackAnimation];
}
lastTimeStamp =curTimeStamp;
[NSThread sleepForTimeInterval:0.1f];
}
}
);
}
-(void) hackAnimation
{
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(hackAnimation) withObject:nil waitUntilDone:NO];
}
#synchronized(currentLocationMarker)
{
GMSMarker* theMarker = currentLocationMarker;
UIImage* curImage = [UIImage imageNamed:#"myMarker.png"];
UIImage* scaled = [UIImage imageWithData:UIImagePNGRepresentation(curImage) scale:curScaleFactor];
theMarker.icon=scaled;
}
}
If you want to animate marker grow once it's tapped instead of having bouncy animation, init your marker image like this:
kStartingImageScale = 3.0 // make it available in your class.
marker.icon = UIImage(data: UIImagePNGRepresentation(UIImage(named: "marker")!)!, scale: CGFloat(kStartingImageScale))!
Then use a little modified method by #MichaelCMS
func startAnimation(shouldGrow:Bool) {
let scaleMin:Double = 2.0;
let scaleMax:Double = kStartingImageScale;
var curScaleFactor:Double = shouldGrow ? scaleMax: scaleMin;
var curScaleDirection:Double = 1.0;
let scaleStep:Double = 4.0;
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
var runAnimation: Bool = true
var lastTimeStamp:Double?
while (runAnimation)
{
let curTimeStamp:Double = NSDate().timeIntervalSince1970
var delta:Double;
if (lastTimeStamp>0)
{
delta = curTimeStamp - lastTimeStamp!;
curScaleFactor+=scaleStep*curScaleDirection*delta;
if (curScaleFactor < scaleMin)
{
curScaleFactor = scaleMin;
curScaleDirection = curScaleDirection*(-1.0);
runAnimation = !shouldGrow
}
else if (curScaleFactor>scaleMax)
{
curScaleFactor = scaleMax;
curScaleDirection = curScaleDirection*(-1.0);
runAnimation = shouldGrow
}
self.hackAnimation(curScaleFactor)
}
lastTimeStamp = curTimeStamp;
NSThread.sleepForTimeInterval(0.01)
}
}
}
func hackAnimation(curScaleFactor:Double)
{
let theMarker:GMSMarker = self.marker!;
let curImage:UIImage = UIImage(named: "marker")!
let scaled:UIImage = UIImage(data: UIImagePNGRepresentation(curImage)!, scale: CGFloat(curScaleFactor))!
dispatch_async(dispatch_get_main_queue(), { () -> Void in
theMarker.icon=scaled;
})
}
// MARK: - GMSMapViewDelegate
var grooow:Bool = true;
func mapView(mapView: GMSMapView!, didTapMarker marker: GMSMarker!) -> Bool {
DDLogDebug("Marker tapped \(marker)")
startAnimation(grooow)
grooow = !grooow
return true
}

How to set screen brightness with fade animations?

Is it possible to animate the screen brightness change on iOS 5.1+? I am using [UIScreen mainScreen] setBrightness:(float)] but I think that the abrupt change is ugly.
I ran into issues with the accepted answer when attempting to animate to another value with a previous animation in progress. This solution cancels an in-progress animation and animates to the new value:
extension UIScreen {
func setBrightness(_ value: CGFloat, animated: Bool) {
if animated {
_brightnessQueue.cancelAllOperations()
let step: CGFloat = 0.04 * ((value > brightness) ? 1 : -1)
_brightnessQueue.add(operations: stride(from: brightness, through: value, by: step).map({ [weak self] value -> Operation in
let blockOperation = BlockOperation()
unowned let _unownedOperation = blockOperation
blockOperation.addExecutionBlock({
if !_unownedOperation.isCancelled {
Thread.sleep(forTimeInterval: 1 / 60.0)
self?.brightness = value
}
})
return blockOperation
}))
} else {
brightness = value
}
}
}
private let _brightnessQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return queue
}()
Swift 5
import UIKit
extension UIScreen {
public func setBrightness(to value: CGFloat, duration: TimeInterval = 0.3, ticksPerSecond: Double = 120) {
let startingBrightness = UIScreen.main.brightness
let delta = value - startingBrightness
let totalTicks = Int(ticksPerSecond * duration)
let changePerTick = delta / CGFloat(totalTicks)
let delayBetweenTicks = 1 / ticksPerSecond
let time = DispatchTime.now()
for i in 1...totalTicks {
DispatchQueue.main.asyncAfter(deadline: time + delayBetweenTicks * Double(i)) {
UIScreen.main.brightness = max(min(startingBrightness + (changePerTick * CGFloat(i)),1),0)
}
}
}
}
I don't know if this is "animatable" in some other way, but you could do it yourself.
For instance the following example code was hooked up to "Full Bright" and "Half Bright" buttons in the UI. It uses performSelector...afterDelay to change the brightness by 1% every 10ms till the target brightness is reached. You would pick an appropriate change rate based on some experimenting. Actually the refresh rate is, I think, 60 hz so there is probably no point in doing a change at an interval smaller than 1/60th of a second (My example rate was chosen to have nice math). Although you might want to do this on a non-UI thread, it doesn't block the UI.
- (IBAction)fullBright:(id)sender {
CGFloat brightness = [UIScreen mainScreen].brightness;
if (brightness < 1) {
[UIScreen mainScreen].brightness += 0.01;
[self performSelector:#selector(fullBright:) withObject:nil afterDelay:.01];
}
}
- (IBAction)halfBright:(id)sender {
CGFloat brightness = [UIScreen mainScreen].brightness;
if (brightness > 0.5) {
[UIScreen mainScreen].brightness -= 0.01;
[self performSelector:#selector(halfBright:) withObject:nil afterDelay:.01];
}
}
A Swift extension:
extension UIScreen {
private static let step: CGFloat = 0.1
static func animateBrightness(to value: CGFloat) {
guard fabs(UIScreen.main.brightness - value) > step else { return }
let delta = UIScreen.main.brightness > value ? -step : step
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
UIScreen.main.brightness += delta
animateBrightness(to: value)
}
}
}
Based on Charlie Price's great answer, here's a method for "animating" a change in screen brightness to any value desired.
- (void)graduallyAdjustBrightnessToValue:(CGFloat)endValue
{
CGFloat startValue = [[UIScreen mainScreen] brightness];
CGFloat fadeInterval = 0.01;
double delayInSeconds = 0.01;
if (endValue < startValue)
fadeInterval = -fadeInterval;
CGFloat brightness = startValue;
while (fabsf(brightness-endValue)>0) {
brightness += fadeInterval;
if (fabsf(brightness-endValue) < fabsf(fadeInterval))
brightness = endValue;
dispatch_time_t dispatchTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(dispatchTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[UIScreen mainScreen] setBrightness:brightness];
});
}
}
Or you can use NSTimer instead of while loops and performSelector.
finalValue - is value you want to achieve.
Timer fires 30 times with duration 0.02 second for each (you can choose something different but smoothly) and changes brightness value.
weak var timer: NSTimer?
var count = 1
let maxCount = 30
let interval = 0.02
timer = NSTimer
.scheduledTimerWithTimeInterval(interval,
target: self,
selector: #selector(changeBrightness),
userInfo: nil,
repeats: true)
func changeBrightness()
{
guard count < maxCount else { return }
let currentValue = UIScreen.mainScreen().brightness
let restCount = maxCount - count
let diff = (finalValue - currentValue) / CGFloat(restCount)
let newValue = currentValue + diff
UIScreen.mainScreen().brightness = newValue
count += 1
}
You can use this helper if you need to change brightness of your specific ViewController
import Foundation
import UIKit
final class ScreenBrightness {
private var timer: Timer?
private var brightness: CGFloat?
private var isBrighteningScreen = false
private var isDarkeningScreen = false
private init() { }
static let shared = ScreenBrightnessHelper()
//Увеличение яркости экрана до максимального уровня
func brightenDisplay() {
resetTimer()
isBrighteningScreen = true
if #available(iOS 10.0, *), timer == nil {
brightness = UIScreen.main.brightness
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { (timer) in
UIScreen.main.brightness = UIScreen.main.brightness + 0.01
if UIScreen.main.brightness > 0.99 || !self.isBrighteningScreen {
self.resetTimer()
}
}
}
timer?.fire()
}
//Затемнение экрана до предыдущего уровня
func darkenDisplay() {
resetTimer()
isDarkeningScreen = true
guard let brightness = brightness else {
return
}
if #available(iOS 10.0, *), timer == nil {
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { (timer) in
UIScreen.main.brightness = UIScreen.main.brightness - 0.01
if UIScreen.main.brightness <= brightness || !self.isDarkeningScreen {
self.resetTimer()
self.brightness = nil
}
}
timer?.fire()
}
}
private func resetTimer() {
timer?.invalidate()
timer = nil
isBrighteningScreen = false
isDarkeningScreen = false
}
}
Call ScreenBrightness.shared.brightenDisplay() in viewWillAppear and if you wanna change it back call method ScreenBrightness.shared.darkenDisplay() that will change brightness back

Resources